Why does my OS won't boot but writes a space before the cursor in QEMU? (long question) [duplicate] - c

I've been banging my head against the wall in an attempt to understand why the following assembly is not correctly dumping the contents of 'HELLO_WORLD'.
; Explicitly set 16-bit
[ BITS 16 ]
[ ORG 0x7C00 ]
; Create label for hello world string terminated by null.
HELLO_WORLD db 'hello world', 0
start:
; Move address of HELLO_WORLD into si
mov SI, HELLO_WORLD
call print_string
; Continue until the end of time
jmp $
print_string:
loop:
; Retrieve value stored in address at si
mov al, [SI]
mov ah, 0x0E
cmp al, 0
; Finish execution after hitting null terminator
je return
INT 0x10
; Increment contents of si (address)
inc SI
jmp loop
return:
ret
; boot loader length *must* be 512 bytes.
times 510-($-$$) db 0
dw 0xAA55
In the end, I discovered that if we do not execute (make it not code) the label, then it functions correctly.
jmp start
HELLO_WORLD db 'hello world',0
The part I find the most confusing, looking at the hex dump, HELLO_WORLD is still in the binary (at the beginning - and there appears to be no distinction of its type).
cat nojmp_boot.out
00000000 68 65 6c 6c 6f 20 77 6f 72 6c 64 00 be 00 7c e8 |hello world...|.|
00000010 02 00 eb fe 8a 04 b4 0e 3c 00 74 05 cd 10 46 eb |........<.t...F.|
00000020 f3 c3 eb e8 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200
cat jmpboot.out
00000000 eb 22 68 65 6c 6c 6f 20 77 6f 72 6c 64 00 be 02 |."hello world...|
00000010 7c e8 02 00 eb fe 8a 04 b4 0e 3c 00 74 05 cd 10 ||.........<.t...|
00000020 46 eb f3 c3 eb e8 00 00 00 00 00 00 00 00 00 00 |F...............|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200
Inspecting the first two bytes, we can see 'e8 22' is a shortjump to address 22 (http://net.cs.uni-bonn.de/fileadmin/user_upload/plohmann/x86_opcode_structure_and_instruction_overview.pdf).
My question is:
Why can we not have 'HELLO_WORLD' as a part of the execution of the program, as far I was concerned, there was no distinction between code and data?
I'm using the following for compilation:
nasm -f bin -o boot.bin boot.asm && if [ $(stat -c "%s" boot.bin) -ne 512 ]; then x; fi && qemu-system-x86_64 boot.bin

Execution starts at the top. If you omit the jmp start then the character h will get interpreted by the CPU as if it were an instruction. Surely you see that such can not be correct?
as far I was concerned, there was no distinction between code and data?
There's no distinction between code and data when we consider their placement in the binary. But code and data still remain 2 completly different items. Code being the only one that can get executed by the CPU.

Since you're creating a boot sector the execution begins at the first byte of the generated file. It won't begin at the start label or anywhere else. Since the string "hello world" is at the start of the file these bytes are what get executed first. These bytes are interpreted by the CPU as instructions, not characters, and they get executed as whatever instructions they get decoded as.
Here are the instructions that get executed:
7c00: 68 65 6c push 0x6c65
7c03: 6c ins BYTE PTR es:[di],dx
7c04: 6f outs dx,WORD PTR ds:[si]
7c05: 20 77 6f and BYTE PTR [bx+0x6f],dh
7c08: 72 6c jb 0x7c76
7c0a: 64 00 be 00 7c add BYTE PTR fs:[bp+0x7c00],bh
7c0f: e8 02 00 call 0x7c14
7c12: eb fe jmp 0x7c12
7c14: 8a 04 mov al,BYTE PTR [si]
...

Related

C program in Docker: fwrite(3) and write(2) fail to modify files on Windows but not on MacOS

I am writing a guest OS on top of Linux (Ubuntu distribution) within a Docker container. The filesystem is implemented as a single file resting inside the host OS, so anytime a file is changed in the guest OS filesystem, the file on the host OS must be opened, the correct block(s) must be overwritten, and the file must be closed.
My partner and I have developed the following recursive helper function to take in a block number and offset to abstract away all details at the block-level for higher level functions:
/**
* Recursive procedure to write n bytes from buf to the
* block specified by block_num. Also updates FAT to
* reflect changes.
*
* #param block_num identifier for block to begin writing
* #param buf buffer to write from
* #param n number of bytes to write
* #param offset number of bytes to start writing from as
* measured from start of file
*
* #returns number of bytes written
*/
int write_bytes(int block_num, const char *buf, int n, int offset) {
BlockTuple red_tup = reduce_block_offset(block_num, offset);
block_num = red_tup.block;
offset = red_tup.offset;
FILE *fp = fopen(fat->fname, "r+");
int bytes_to_write = min(n, fat->block_size - offset);
int write_n = max(bytes_to_write, 0);
fseek(fp, get_block_start(block_num) + offset, SEEK_SET);
fwrite(buf, 1, write_n, fp); // This line is returning 48 bytes written
fclose(fp);
// Check if there are bits remaining
int bytes_left = n - write_n;
if (bytes_left > 0) {
// Recursively write on next block
int next_block = get_free_block();
set_fat_entry(block_num, next_block); // point block to next block
set_fat_entry(next_block, 0xFFFF);
return write_bytes(next_block, buf + write_n, bytes_left, max(0, offset - fat->block_size)) + write_n;
} else {
set_fat_entry(block_num, 0xFFFF); // mark file as terminated
return write_n;
}
}
The issue is that fwrite(3) is reporting 48 bytes written (when n is passed as 48) but hexdumping the file on the host OS reveals no bytes have been changed:
00000000 00 01 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008000
This is particularly wacky because when my partner runs the code on the exact same commit (with no uncommitted changes), her write goes through and the file on the host OS hexdumps to:
00000000 00 01 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000100 66 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |f1..............|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 00 01 00 00 02 00 01 06 e7 36 75 63 00 00 00 00 |.........6uc....|
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200 e0 53 f8 88 c0 0d 37 ca 84 1f 19 b0 6c a8 68 7b |.S....7.....l.h{|
00000210 57 83 cf 13 f0 42 21 d3 21 e1 da de d4 8a f1 e6 |W....B!.!.......|
00000220 f0 12 98 fb 1c 30 4c 04 b3 16 1d 96 17 ba d7 5a |.....0L........Z|
00000230 7e f3 8a f5 6a 42 6b ef 58 f6 bc 01 db 0c 02 53 |~...jBk.X......S|
00000240 e5 10 7e f3 4a d5 3f ac 8e 38 82 c3 95 f8 11 8e |..~.J.?..8......|
00000250 a6 82 eb 3b 24 56 9a 75 44 36 8b 25 60 83 4c 04 |...;$V.uD6.%`.L.|
00000260 07 9e 14 99 9c 9f 87 3c 8a d4 c3 e8 17 60 81 0e |.......<.....`..|
00000270 bc eb 1d 35 68 fc d5 be 4f 1c 9d 5e 72 57 65 01 |...5h...O..^rWe.|
00000280 b7 43 54 26 d6 6d ba 51 bf 12 8c a1 03 d5 66 b3 |.CT&.m.Q......f.|
00000290 90 0d 60 b8 95 8d 15 bd 53 9a 70 77 4f 7a 04 1e |..`.....S.pwOz..|
000002a0 9e b2 4c 9a 79 dd de 48 cd fe 1e dc 57 7d d1 7f |..L.y..H....W}..|
000002b0 3f f5 77 96 fa e7 d7 33 33 48 ce 0a 4d 61 ab 96 |?.w....33H..Ma..|
000002c0 5f c4 88 bf c6 3a 09 37 76 c4 b8 db bc 6a 7d c0 |_....:.7v....j}.|
000002d0 c4 89 68 e7 b4 70 f8 a6 a8 00 9d c4 63 da fb 66 |..h..p......c..f|
000002e0 be d2 cd 68 1c d2 ff bf 00 e9 37 ab 6b 1a 3c f2 |...h......7.k.<.|
000002f0 7b c1 a2 c4 46 ae db 93 b4 4f 64 79 14 2a 1a d4 |{...F....Ody.*..|
00000300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008000
The 48 bytes I'm referring to that don't get written are the bytes written to the directory block running from address 00000100-0000012E (the bytes below that represent the actual file being written, the code seg faults on my end before reaching that write). It's worth noting my container can still format the filesystem file, so all writes aren't broken. This snippet just represents the first write that did not work.
We are both running the code in an identical Docker container. The only difference I could imagine is my computer is a Windows and hers is a Mac. What could possibly be the issue?
The very first thing I believed was that there was some conflict with the host OS that blocked my write, but assigning and printing the return value of fwrite(3) returned that 48 bytes were indeed written on both machines.
I was also expecting that my buffer was simply all 0s (it is initially allocated using calloc(3)), but printing out the first 48 bytes of the buffer proved that theory false.
I finally considered that this was some issue with the higher level interface in <stdio.h> instead of the lower level one in <unistd.h>. I replaced fopen(3), fwrite(3), flseek(3), fclose(3) each with their lower-level equivalents (write(2) etc) and it still turned up 48 bytes written with no actual change to the files.
EDIT:
The guest OS filesystem can be formatted with respect to user parameters. All testing above was performed with a block size of 256 bytes and 128 blocks total. I've attempted the exact same write sequence again with a block size of 1024 bytes and 16384 blocks total, and there was no error. It's still unclear why the code works on my partner's machine for both format configs and not mine, but this may narrow it down.
Running strace reveals the following excerpt around the write:
openat(AT_FDCWD, "minfs", O_RDWR) = 4
newfstatat(4, "", {st_mode=S_IFREG|0777, st_size=32768, ...}, AT_EMPTY_PATH) = 0
lseek(4, 0, SEEK_SET) = 0
read(4, "\0\1\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 256) = 256
write(4, "f1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 48) = 48
close(4)
It again appears the bytes get written, but a hd after the program finishes reveals the same output as above. My thought was perhaps the bytes written in the excerpt are overwritten later on, but the only write after the excerpt above in the strace is:
lseek(4, 0, SEEK_SET) = 0
read(4, "\0\1\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 320) = 320
write(4, "\0", 1) = 1
close(4)
which should be at address 320, squarely after the write at address 256 above.
It turns out the mismatch was due to undefined behavior concerning when changes were synchronized in mmap(2). There was a section of code where a region of memory mapped via mmap(2) was changed and then immediately followed by reads/writes to the file on the host OS containing the mapped region of memory. It seems the Mac would write through the changes before the following section while the Windows wouldn't synchronize until after the fact, resulting in the undefined behavior.
The problem was fixed by making a call to msync(2) immediately after modifying the mapped region from mmap(2) with the MS_SYNC flag forcing the write-through behavior.
Links to documentation here: mmap(2), msync(2).

Finding the value of pointers in memory C

Given the dataset below and the answer shown at the bottom (b36d5c), how does one compute this value for &ptr[3]. I'm just having trouble figuring out which area of the dataset it is referring to and how you add the size to that, but I know that the answer is correct.
sizeof(int) = 4
Data set starting at 0xb36d00 of size 128.
0xb36d00: 48 6d b3 00 00 00 00 00 4c 6d b3 00 00 00 00 00 Hm......Lm......
0xb36d10: 50 6d b3 00 00 00 00 00 30 6d b3 00 00 00 00 00 Pm......0m......
0xb36d20: 04 6d b3 00 00 00 00 00 50 6d b3 00 00 00 00 00 .m......Pm......
0xb36d30: 18 6d b3 00 00 00 00 00 1c 6d b3 00 00 00 00 00 .m.......m......
0xb36d40: 48 6d b3 00 00 00 00 00 58 6d b3 00 00 00 00 00 Hm......Xm......
0xb36d50: 44 6d b3 00 00 00 00 00 4c 6d b3 00 00 00 00 00 Dm......Lm......
0xb36d60: 00 6d b3 00 00 00 00 00 0c 6d b3 00 00 00 00 00 .m.......m......
0xb36d70: 68 6d b3 00 00 00 00 00 30 6d b3 00 00 00 00 00 hm......0m......
int array[] is stored at: 0xb36d00
int *ptr is stored at: 0xb36d28
short *shortPtr is stored at: 0xb36d28
int x is stored at: 0xb36d38
short y is stored at: 0xb36d14
int ***what is defined by: (int ***)(((int **)0xb36d0c) + 2)
What is the value of &ptr[3]?
b36d5c
int *ptr is said to be stored at 0xb36d28. The memory at 0xb36d28 contains 0x00b36d50 in little-endian order (and it contains that value regardless of whether pointers are four-bytes or eight, as the next four bytes are zero). Thus, ptr is 0xb36d50. If pointers are four bytes, then the address of ptr[3] is 3•4 bytes beyond 0xb36d50, so it is 0xb36d5c. (Since this fits the known answer, we conclude pointers are four bytes, not eight.)

How to read binary executable by instructions?

is there a way to read given amount of instructions from a binary executable file on x86 architecture programmatically?
If I had a binary of a simple C program hello.c:
#include <stdio.h>
int main(){
printf("Hello world\n");
return 0;
}
Where after compilation using gcc, the disassembled function main looks like this:
000000000000063a <main>:
63a: 55 push %rbp
63b: 48 89 e5 mov %rsp,%rbp
63e: 48 8d 3d 9f 00 00 00 lea 0x9f(%rip),%rdi # 6e4 <_IO_stdin_used+0x4>
645: e8 c6 fe ff ff callq 510 <puts#plt>
64a: b8 00 00 00 00 mov $0x0,%eax
64f: 5d pop %rbp
650: c3 retq
651: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
658: 00 00 00
65b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Is there an easy way in C to read for example first three instructions (meaning the bytes 55, 48, 89, e5, 48, 8d, 3d, 9f, 00, 00, 00) from main? It is not guaranteed that the function looks like this - the first instructions may have all different opcodes and sizes.
this prints the 10 first bytes of the main function by taking the address of the function and converting to a pointer of unsigned char, print in hex.
This small snippet doesn't count the instructions. For this you would need an instruction size table (not very difficult, just tedious unless you find the table already done, What is the size of each asm instruction?) to be able to predict the size of each instruction given the first byte.
(unless of course, the processor you're targetting has a fixed instruction size, which makes the problem trivial to solve)
Debuggers have to decode operands as well, but in some cases like step or trace, I suspect they have a table handy to compute the next breakpoint address.
#include <stdio.h>
int main(){
printf("Hello world\n");
const unsigned char *start = (const char *)&main;
int i;
for (i=0;i<10;i++)
{
printf("%x\n",start[i]);
}
return 0;
}
output:
Hello world
55
89
e5
83
e4
f0
83
ec
20
e8
seems to match the disassembly :)
00401630 <_main>:
401630: 55 push %ebp
401631: 89 e5 mov %esp,%ebp
401633: 83 e4 f0 and $0xfffffff0,%esp
401636: 83 ec 20 sub $0x20,%esp
401639: e8 a2 01 00 00 call 4017e0 <___main>
.globl _start
_start:
bl main
b .
.globl main
main:
add r1,#1
add r2,#1
add r3,#1
add r4,#1
b main
intentionally wrong architecture, architecture doesnt matter file format matters. built this into an elf file format, which is very popular, and is simply a file format which is what I understood your question to be, to read a file, not modify the binary to read the program runtime from memory.
it is very much popular and there are tools that do it which you appear to know how to run.
Disassembly of section .text:
00001000 <_start>:
1000: eb000000 bl 1008 <main>
1004: eafffffe b 1004 <_start+0x4>
00001008 <main>:
1008: e2811001 add r1, r1, #1
100c: e2822001 add r2, r2, #1
1010: e2833001 add r3, r3, #1
1014: e2844001 add r4, r4, #1
1018: eafffffa b 1008 <main>
if I hexdump the file though
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 28 00 01 00 00 00 00 10 00 00 34 00 00 00 |..(.........4...|
00000020 c0 11 00 00 00 02 00 05 34 00 20 00 01 00 28 00 |........4. ...(.|
00000030 06 00 05 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 1c 10 00 00 1c 10 00 00 05 00 00 00 |................|
00000050 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2 |............. ..|
00001010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11 00 00 |.0...#......A...|
00001020 00 61 65 61 62 69 00 01 07 00 00 00 08 01 00 00 |.aeabi..........|
00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001040 00 00 00 00 00 10 00 00 00 00 00 00 03 00 01 00 |................|
00001050 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 00 |................|
00001060 01 00 00 00 00 00 00 00 00 00 00 00 04 00 f1 ff |................|
00001070 06 00 00 00 00 10 00 00 00 00 00 00 00 00 01 00 |................|
00001080 18 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
00001090 09 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
000010a0 17 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
000010b0 55 00 00 00 00 10 00 00 00 00 00 00 10 00 01 00 |U...............|
000010c0 23 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |#...............|
000010d0 2f 00 00 00 08 10 00 00 00 00 00 00 10 00 01 00 |/...............|
000010e0 34 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |4...............|
000010f0 3c 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |<...............|
00001100 43 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |C...............|
00001110 48 00 00 00 00 00 08 00 00 00 00 00 10 00 01 00 |H...............|
00001120 4f 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |O...............|
00001130 00 73 6f 2e 6f 00 24 61 00 5f 5f 62 73 73 5f 73 |.so.o.$a.__bss_s|
00001140 74 61 72 74 5f 5f 00 5f 5f 62 73 73 5f 65 6e 64 |tart__.__bss_end|
00001150 5f 5f 00 5f 5f 62 73 73 5f 73 74 61 72 74 00 6d |__.__bss_start.m|
00001160 61 69 6e 00 5f 5f 65 6e 64 5f 5f 00 5f 65 64 61 |ain.__end__._eda|
00001170 74 61 00 5f 65 6e 64 00 5f 73 74 61 63 6b 00 5f |ta._end._stack._|
00001180 5f 64 61 74 61 5f 73 74 61 72 74 00 00 2e 73 79 |_data_start...sy|
00001190 6d 74 61 62 00 2e 73 74 72 74 61 62 00 2e 73 68 |mtab..strtab..sh|
000011a0 73 74 72 74 61 62 00 2e 74 65 78 74 00 2e 41 52 |strtab..text..AR|
000011b0 4d 2e 61 74 74 72 69 62 75 74 65 73 00 00 00 00 |M.attributes....|
000011c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000011e0 00 00 00 00 00 00 00 00 1b 00 00 00 01 00 00 00 |................|
000011f0 06 00 00 00 00 10 00 00 00 10 00 00 1c 00 00 00 |................|
00001200 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 |................|
00001210 21 00 00 00 03 00 00 70 00 00 00 00 00 00 00 00 |!......p........|
00001220 1c 10 00 00 12 00 00 00 00 00 00 00 00 00 00 00 |................|
00001230 01 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................|
00001240 00 00 00 00 00 00 00 00 30 10 00 00 00 01 00 00 |........0.......|
00001250 04 00 00 00 05 00 00 00 04 00 00 00 10 00 00 00 |................|
00001260 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00001270 30 11 00 00 5c 00 00 00 00 00 00 00 00 00 00 00 |0...\...........|
00001280 01 00 00 00 00 00 00 00 11 00 00 00 03 00 00 00 |................|
00001290 00 00 00 00 00 00 00 00 8c 11 00 00 31 00 00 00 |............1...|
000012a0 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
000012b0
can google the file format and find a lot of info at wikipedia, with a smidge more at one of the links
useful header information
00 10 00 00 entrh
34 00 00 00 phoff
c0 11 00 00 shoff
00 02 00 05 flags
34 00 ehsize
20 00 phentsize
01 00 phnum
28 00 shentsize
06 00 shnum
05 00shstrndx
so if I look at the beginning of the sections there are shnum number of them
0x11C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x11E8 1b 00 00 00 01 00 00 00 06 00 00 00 00 10 00 00 00 10 00 00
0x1210 21 00 00 00 03 00 00 70 00 00 00 00 00 00 00 00 1c 10 00 00
0x1238 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 30 10 00 00
0x1260 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 30 11 00 00
0x1288 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 8c 11 00 00
0x1260 strtab type offset 0x1130 which is broken into null terminated strings until you hit a double null
[0] 00
[1] 73 6f 2e 6f 00 so.o
[2] 24 61 00 $a
[3] 5f 5f 62 73 73 5f 73 74 61 72 74 5f 5f 00 __bss_start__
[4] 5f 5f 62 73 73 5f 65 6e 64 5f 5f 00 __bss_end__
[5] 5f 5f 62 73 73 5f 73 74 61 72 74 00 __bss_start
[6] 6d 61 69 6e 00 main
...
main is at address 0x115F in the file which is offset 0x2F in the
strtab.
0x1238 symtab starts at 0x1030, 0x10 or 16 bytes per entry
00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001040 00 00 00 00 00 10 00 00 00 00 00 00 03 00 01 00 |................|
00001050 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 00 |................|
00001060 01 00 00 00 00 00 00 00 00 00 00 00 04 00 f1 ff |................|
00001070 06 00 00 00 00 10 00 00 00 00 00 00 00 00 01 00 |................|
00001080 18 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
00001090 09 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
000010a0 17 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................|
000010b0 55 00 00 00 00 10 00 00 00 00 00 00 10 00 01 00 |U...............|
000010c0 23 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |#...............|
000010d0 2f 00 00 00 08 10 00 00 00 00 00 00 10 00 01 00 |/...............|
000010e0 34 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |4...............|
000010f0 3c 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |<...............|
00001100 43 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |C...............|
00001110 48 00 00 00 00 00 08 00 00 00 00 00 10 00 01 00 |H...............|
00001120 4f 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |O...............|
000010d0 2f 00 00 00 has the 0x2f offset in the symbol table
so this is main, from this entry the address 08 10 00 00 or 0x1008 in
the processors memory, unfortunately due to the values I chose it happens to also be the file offset, dont get that confused.
this section is type 00000001 PROGBITS
0x11E8 1b 00 00 00 01 00 00 00 06 00 00 00 00 10 00 00 00 10 00 00
offset 0x1000 in the file 0x1C bytes
here is the program, the machine code.
00001000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2
00001010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11
so starting at memory offset 0x1008 which is 8 bytes after the
entry point (unfortunately I picked a bad address to use) we need to
go 0x8 bytes offset into this data
01 10 81 e2 01 20 82 e2
00001008 <main>:
1008: e2811001 add r1, r1, #1
100c: e2822001 add r2, r2, #1
1010: e2833001 add r3, r3, #1
this is all very file dependent, the cpu could care less about labels, main only means something to the humans, not the cpu.
If I convert the elf into other formats which are perfectly executable:
motorola s record:
S00A0000736F2E7372656338
S1131000000000EBFEFFFFEA011081E2012082E212
S10F1010013083E2014084E2FAFFFFEAB1
S9031000EC
raw binary image
hexdump -C so.bin
00000000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2 |............. ..|
00000010 01 30 83 e2 01 40 84 e2 fa ff ff ea |.0...#......|
0000001c
The instruction bytes of interest are of course there, but the symbol information isnt. It depends on the file format you are interested in as to 1) if you can find "main" and then 2) print out the first few bytes at that address.
Hmm, a bit disturbing, but if you link for 0x2000 gnu ld burns some disk space and puts the offset at 0x2000, but choose 0x20000000 and it burns more disk space but not as much
000100d0 2f 00 00 00 08 00 00 20 00 00 00 00 10 00 01 00
shows the file offset is 0x010010 but the address in target space is 0x20000008
00010010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11 00 00
00010020 00 61 65 61 62 69 00 01 07 00 00 00 08 01
just to demonstrate/enforce the file offset and the target memory space address are two different things.
this is a very nice format for what you are wanting to do
arm-none-eabi-objcopy -O symbolsrec so.elf so.srec
cat so.srec
$$ so.srec
$a $20000000
_bss_end__ $2001001c
__bss_start__ $2001001c
__bss_end__ $2001001c
_start $20000000
__bss_start $2001001c
main $20000008
__end__ $2001001c
_edata $2001001c
_end $2001001c
_stack $80000
__data_start $2001001c
$$
S0090000736F2E686578A1
S31520000000000000EBFEFFFFEA011081E2012082E200
S31120000010013083E2014084E2FAFFFFEA9F
S70520000000DA

Crafting an ELF file using linker scripts without zero-initialized blocks between sections

I am trying to craft a linker-command script to be bootable by legacy grub (using multiboot). I am having difficulty getting the multiboot header in the required location (within the first 8192 bytes). My script looks something like:
SECTIONS
{
.multiboot :
{
__multiboot_header = .;
*(.multiboot)
}
.text 0x00100000 :
{
*(.text*)
*(.rodata)
}
/* ... remainder of script ... */
}
Overall, my objective is to have my custom executable loaded by the bootloader after the first 1MiB of physical memory; the address as part of the .text section declaration seems to have done this as I expected. Reading the ELF header gives the entry point as:
$ readelf -h kernel.elf | grep Entry
Entry point address: 0x100000
However, in doing so I seem to have also increased the file by this much.
$ ls -l file.elf
-rwxr-xr-x 1 user user 1049960 May 13 02:20 file.elf
The area between the ELF header and the .text section is initialized to zeros.
$ hexdump -C file.elf
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 00 00 10 00 34 00 00 00 |............4...|
00000020 00 04 10 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.|
00000030 09 00 08 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 f8 00 10 00 f8 f0 4e 00 07 00 00 00 |..........N.....|
00000050 00 00 20 00 51 e5 74 64 00 00 00 00 00 00 00 00 |.. .Q.td........|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 |................|
00000070 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00100000 8b 25 f8 f0 4e 00 50 53 e8 66 00 00 00 fa f4 eb |.%..N.PS.f......|
00100010 fc 55 89 e5 53 83 ec 10 c7 45 f8 00 00 00 00 eb |.U..S....E......|
00100020 38 a1 f4 00 10 00 8b 55 f8 01 d2 01 d0 8b 15 f4 |8......U........|
Also, although readelf -s reports that __multiboot_header has a value of 0x0 (which should be the address of the structure since it was defined at the same point it was mentioned in the linker file, right?):
$ readelf -s kernel.elf | grep multiboot
22: 00000000 0 NOTYPE GLOBAL DEFAULT 1 __multiboot_header
The output of readelf -S seems to conflict:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 1] .multiboot PROGBITS 00000000 1000f8 00000c 00 0 0 1
[ 2] .text PROGBITS 00100000 100000 000096 00 AX 0 0 1
Which implies that the .multiboot section is actually inside the .text section.
If I offset into the file by 0x1000f8 then I can find the structure, however, I am not sure where the offset came from.
tl;dr
1) How can I ensure a specific data structure is within the first 8192 bytes of the output file?
2) How can I specify the load address without inflating the output binary with large gaps of zero-initialized blocks?
The elf file format is generated by its own rules, how it stores the information is not directly influenced by the linker file (e.g. at what offsets the sections are stored must not correlate with their location at lifetime in memory). It's the memory layout which is described by the SECTIONS command in your linker file, and the elf file format describes this layout... You need a elf capable loader to load the individual sections into the target locations.
To get a flat binary file which can be loaded 1:1 into memory, use objcopy (e.g. something like objcopy -O binary myfile.elf myfile.bin). The layout of that file is directly influenced by your linker script, and the content of the .multiboot section should be really at offset 0.

Write header with C program?

In order to boot a Linux kernel on an embedded device I have to tag the kernel with a special header. The program used to tag the kernel is provided by the manufacture of the device as a 32-bit binary only. This is very annoying as I have to install hundreds of megabytes libraries on my 64-bit system only to tag a kernel with few bytes. This is how the kernel is tagged:
$./mkimage -f kernel.cfg -d zImage_without_header zImage
kernel.cfg:
##########################################################
#ENCINFO.CFG
#
# information and command for encode the Linux zImage
##########################################################
# Magic number for the ImageHeader, use this to seach start of the Image Header
#
MAGIC_NUMBER 0x27051956
#operation system type
OS_TYPE linux
#cpu architecture type
CPU_ARCH arm
#image type
IMAGE_TYPE kernel
#compress type
COMPRESS_TYPE gzip
#
DATALOAD_ADDRESS 0x00008000
#
ENTRY_ADDRESS 0x00008000
#image name string
IMAGE_NAME kernel.img
#model name string
MODEL_NAME DNS-313
# version string
VERSION 1.00b18
# mac address string
MAC_ADDRESS FF-FF-FF-FF-FF-FF
#the beginning offset of writing header
START_OFFSET 0x00
#the end offset of writing header
END_OFFSET 0xFF
#whether overwrite
OVERWRITE n
The mkimage binary is different from the mkimage that is available from e.g. the Debian repository, that one will not work for my device. I have tried to create a 1MB file and tagged it to display the header:
$dd if=/dev/zero bs=1k count=1024 of=zImage_without_header
$./mkimage -f kernel.cfg -d zImage_without_header zImage
output from last command:
Magic Number: 27051956
Image Name: kernel.img
Created: Wed May 2 17:40:43 2012
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 1048576 Bytes = 1024.00 kB = 1.00 MB
Load Address: 0x00008000
Entry Point: 0x00008000
Model Name: DNS-313
Version : 1.00b18
Mac Address: ff:ff:ff:ff:ff:ff
$hexdump -C zImage
output from last command:
00000000 27 05 19 56 [2c 83 53 d5] 4f a1 [55 7b 00 10 00 00] |'..V,.S.O.U{....|
00000010 00 00 80 00 00 00 80 00 [a7 38 ea 1c] 05 02 02 01 |.........8......|
00000020 6b 65 72 6e 65 6c 2e 69 6d 67 00 00 00 00 00 00 |kernel.img......|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 44 4e 53 2d 33 31 33 00 00 00 00 00 00 00 00 00 |DNS-313.........|
00000050 31 2e 30 30 62 31 38 00 00 00 00 00 00 00 00 00 |1.00b18.........|
00000060 ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 |................|
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00100060
The kernels should always be tagged with a header like the one above as I do not need to change anything. The the values enclosed in brackets [] seem to change when the filesize does, but I do not know how.
I think that the same thing could be accomplished with a small C program, but I am not sure where to start and how?
Any suggestions or ideas are welcome.
It might be a long shot, but if you do not have access to the "mkimage" source code, you can try disassembling it with objdump and try to figure out what is going on :
$ objdump -d ./mkimage

Resources