43

I am not new to bootloader and system SW, but I don't know the origin of the reason why the general program starts at 0x8000. I already know the address 0x8000 has been used as start address in normal C/C++ program.

Does the minimum size of the bootloader for a general program take up to 0x8000? Or is the minimum block size of ROM that should be allocated to the bootloader 32KB? Or is there another reason?

I'd like to know about this, historically or logically, and from a virtual address point of view.


I appreciate all, your time and help with this. To make question more clear, the question is related with virtual address not with physical.

I basically agree with R's opinion from physical memory address point of view.

Without saying a specific system which is diverse, for example linux (even in android), general RTOS (nucleus, and the others, especially ARM linker section), they all use address 0x8000 as start address general program. such named as crt_begin.o, crt.o, etc located at 0x0 with loader exist in this area.

Therefore I guess the minimum size of the bootloader for general program is 32KB considering block size if it would be located at BootROM in boot time(cold boot).

Ummm, But I am not sure...

casperOne
  • 73,706
  • 19
  • 184
  • 253
OfusJK
  • 676
  • 1
  • 5
  • 13
  • 17
    What system are you talking about here? – Jerry Coffin Mar 13 '12 at 06:35
  • 2
    I don't have any reliable source for this, but I can make a qualified guess. Historically many processors, 8-bit in particular, have had the feature called [zero page](http://en.wikipedia.org/wiki/Zero_page) which means that memory cells at addresses 0x00 - 0xFF had instruction support to execute faster. I believe this was introduced by Motorola back in the days, as they had memory-mapped I/O registers on the old MCUs like 6800. -> – Lundin Mar 13 '12 at 08:03
  • 2
    Therefore you would want this first area of memory to be occupied by RAM cells or special registers. It then makes sense that the part of the address space that comes after the zero page is of the same nature: RAM and/or registers. This would take up plenty of kb, maybe up to 0x6000 or some such. I then assume it was convenient to put the ROM (program memory) at a an even address and 0x8000 was convenient. I'm fairly certain that the answer to this question can be found in early Motorola processor designs. – Lundin Mar 13 '12 at 08:04
  • This might be relevant: http://en.wikipedia.org/wiki/ELF_binary. Perhaps the size is to be compatible with previous formats? – Lethargy May 30 '12 at 22:06

5 Answers5

19

In general, on all but the smallest embedded systems, the platform ABI designer wants to avoid ever having the lowest addresses in use so that null pointer dereferences can be trapped. Having several KB of never-valid addresses gives you some additional safety if the null pointer is dereferenced with an array or structure member offset, as in null_ptr->some_member.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 3
    I don't believe this is the reason, I have worked with several embedded systems where address 0 is valid and addressable memory, while at the same time the NVM starts at 8000. – Lundin Mar 13 '12 at 07:36
  • 5
    ...particularly since address 0x8000 existed before the C language and NULL pointers became popular. Perhaps even before C was invented? – Lundin Mar 13 '12 at 08:05
  • As I understand it, you don't want "real" pointers to be 0, ever, even on systems where the HW is ok with it. So, if `malloc()` returns 0, you know it failed. So on systems where address 0 doesn't trap, the memory is usually allocated for specific purposes such as interrupt handlers. – MSalters Mar 13 '12 at 08:14
  • 5
    @MSalters `malloc()` returns a null pointer if it fails. There's no requirement that a null pointer be the address 0 (only that an integral constant evaluating to 0 convert implicitly to it, whatever it is). And where the system puts things like interrupt handlers depends on the hardware---Intel has always put them at the very beginning, but other systems varied. – James Kanze Mar 13 '12 at 08:35
  • 2
    @JamesKanze: Of course, but the "embedded systems" where "HW is ok with it" are simple systems without VM (if you do have a VM, you just don't map `(void*)NULL` in the process space). On those systems, you're so close to the metal that you really want `NULL` to be bitwise 0. And yes, interrupt handlers are one example; other CPU's had memory-mapped I/O registers at address 0. – MSalters Mar 13 '12 at 08:45
  • 2
    @MSalters With VM, the simplest solution is to not map the address 0, and use 0 as a null pointer constant. Without VM, systems have used all sorts of things, and there have definitely been systems where the null pointer was not the address 0. – James Kanze Mar 13 '12 at 10:44
  • @James: It would be utterly insane to make the null pointer anything other than all zero bits though - it's useless unless you have hardware that only permits trapping at a different address, it precludes BSS initialization of pointers, and it makes the code for `if (ptr)` much larger on almost any cpu arch. Also it gratuitously breaks a lot of programs that are technically non-portable (e.g. using `memset` to zero pointers) but widely considered acceptable. – R.. GitHub STOP HELPING ICE Mar 13 '12 at 15:08
  • 1
    @R What's the problem with making the null pointer something other than all zero bits? "BSS initialization" is particular to Unix, and doesn't (or didn't) exist in other OS's; it's certainly not relevant to embedded systems without disks. It doesn't change the generated code for `if (ptr)` on most systems (Sparcs are an exception, but Intel isn't). And it's well known (or was, back in the days when we actually used `memset`) that `memset` doesn't work for non-integral types. – James Kanze Mar 13 '12 at 17:24
  • By BSS, I mean any system of avoiding storing implicitly zero-initialized static data on disk. Even DOS executables avoided this by memsetting the BSS in the startup code. It can make a huge difference to binary size for some programs, and would matter for an embedded system with a tiny ROM where programs run loaded into RAM. On almost all systems, the code to compare a register against 0 is shorter (and possibly faster) than comparing against another constant; if nothing else, you have to load the constant but you get 0 for free. – R.. GitHub STOP HELPING ICE Mar 13 '12 at 17:27
  • As for non-integral types, if you assume IEEE floating point, `memset` is definitely safe for floating point. Pointers are the only thing that's left. My argument is that unless you have hardware that can only do illegal-access traps on a special address that's not all zero bits, it would just be at best useless, and at worst harmful, to define the null pointer as anything other than all zero bits. – R.. GitHub STOP HELPING ICE Mar 13 '12 at 17:29
  • @JamesKanze: Even on embedded systems, setting a chunk of RAM to all zeroes is more efficient than having to copy from ROM the initial content of that memory. Very rarely have I encountered systems where it wasn't desirable to segregating zero-initialized storage from storage requiring other initialization [especially in cases where one can eliminate *all* initialized storage other than the zero-initialized block]. – supercat Nov 21 '13 at 21:24
6

It depends on the system, and programs start at different addresses on different systems. Under Unix, it's usual (or maybe even required by Posix) to use the address 0 as the null pointer, and to not map the first page of virtual memory, so that dereferencing a null pointer will result in a segment violation. I suspect that other systems using address 0 as a null pointer behave similarly (but how much they reserve may vary). (Historically, it was usual to map the first page as read only, and fill it with zeros, do that a null pointer would behave as if it were an empty string, a pointer to "". That's going back about 25 years, however.)

I would expect that even today, some embedded systems do load the program starting at the address 0.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
3

It's somewhat arbitrary, and on linux, at least decided by the linker. The general idea is to reserve some space to catch NULL pointer exceptions. To help prevent kernel space NULL pointer dereferences from executing arbitrary user code in kernel mode, linux prevents you from mapping the very bottom of memory. /proc/sys/vm/mmap_min_addr controls the lowest address you may map (you can change it to 0 and map a page at 0 if you like).

On linux you can look at the memory mapping by looking in /proc. For example,

genwitt ~> cat /proc/self/maps 
00400000-0040c000 r-xp 00000000 08:01 354804                             /bin/cat
0060b000-0060c000 r--p 0000b000 08:01 354804                             /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 354804                             /bin/cat
01dda000-01dfb000 rw-p 00000000 00:00 0                                  [heap]
7f5b25913000-7f5b25a97000 r-xp 00000000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25a97000-7f5b25c97000 ---p 00184000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c97000-7f5b25c9b000 r--p 00184000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c9b000-7f5b25c9c000 rw-p 00188000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c9c000-7f5b25ca1000 rw-p 00000000 00:00 0 
7f5b25ca1000-7f5b25cc2000 r-xp 00000000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25cd2000-7f5b25e97000 r--p 00000000 08:01 126248                     /usr/lib64/locale/locale-archive
7f5b25e97000-7f5b25e9a000 rw-p 00000000 00:00 0 
7f5b25ec0000-7f5b25ec1000 rw-p 00000000 00:00 0 
7f5b25ec1000-7f5b25ec2000 r--p 00020000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25ec2000-7f5b25ec3000 rw-p 00021000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25ec3000-7f5b25ec4000 rw-p 00000000 00:00 0 
7fff18c37000-7fff18c58000 rw-p 00000000 00:00 0                          [stack]
7fff18d0c000-7fff18d0d000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
2

I'd suspect in a lot of cases the first 32K was reserved for the monitors code/ram usage. In a lot of 8051 eval boards it wasn't that uncommon to default to 0x1000 or 0x2000 for all apps depending on the resident monitor (some which worked as debuggers too).

32K might be your u-boot/etc loaders space.

2

I believe the answer is more related to the interrupt handling. The interrupt handler addresses are set in hardware. In Intel 8086, there was a direct translation table on the interrupt handler code and the corresponding interrupt handling routine. Probably, this was done by some combinatorial circuit and hence, to preserve forward compatibility, it would have been more sensible to place them at the starting of the memory rather than at the end to prevent the changes everytime. So, the execution start address would be at the other end of the memory. Also, it was necessary that enough code be contained in that block to load a memory segment program and a jump instruction to switch to execute the code from that code address.