I am trying to understand the relocations for the object file generated by this simple program:
int answer = 42;
int compute()
{
return answer;
}
int main()
{
return compute();
}
I compile it with simply gcc -c main.cpp.
Then, examining objdump -DCrw -M intel main.o, we see among other things:
Disassembly of section .text:
0000000000000000 <compute()>:
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp,rsp
8: 8b 05 00 00 00 00 mov eax,DWORD PTR [rip+0x0] # e <compute()+0xe> a: R_X86_64_PC32 answer-0x4
e: 5d pop rbp
f: c3 ret
(...)
Disassembly of section .data:
0000000000000000 <answer>:
0: 2a 00 sub al,BYTE PTR [rax]
...
We can also look at the relocations as reported by readelf -rW main.o:
Relocation section '.rela.text' at offset 0x250 contains 2 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
000000000000000a 0000000900000002 R_X86_64_PC32 0000000000000000 answer - 4
(...)
Let's look at the relocation for the use of answer in compute():
To return answer from the function, we have this instruction to put the value of answer into eax:
8: 8b 05 00 00 00 00 mov eax,DWORD PTR [rip+0x0]
I.e. "copy whatever is at rip + an offset we don't know yet so let's just put 0s for it for now into eax".
The relocation for that instruction is
Offset Info Type Symbol's Value Symbol's Name + Addend
000000000000000a 0000000900000002 R_X86_64_PC32 0000000000000000 answer - 4
The offset value 0xa means that there's a relocation to be done at 0xa, or the third byte of the mov instruction above which starts on 0x8. This is the placeholder 00 00 00 00 we saw above. The relocation type is R_X86_64_PC32, which according to the System V Application Binary Interface means "S+A-P", where:
S = the value of the symbol whose index resides in the relocation entry
A = the addend used to compute the value of the relocatable field
P = the place (section offset or address) of the storage unit being relocated (computed using r_offset).
The value of the symbol (the value at 0xa) is 0. A is the addend -4 on that line.
I am not certain what P is, is it the address of the .data section?
And where does the -4 come from?
This calculation seems to end up as "4 bytes less the address of the .data section". How does that represent the distance from RIP (0xe) to wherever answer ends up in the final executable?
If I look at the final executable though (gcc -o main main.o, objdump -DCrw -M intel main), I see our mov instruction has been relocated:
1131: 8b 05 d9 2e 00 00 mov eax,DWORD PTR [rip+0x2ed9]
The 00 00 00 00 placeholder has been replaced with d9 2e 00 00. rip is now at 0x1137, so rip+0x2ed9 is 0x4010. And sure enough, at 0x4010 we find the answer:
Disassembly of section .data:
(...)
0000000000004010 <answer>:
4010: 2a 00 sub al,BYTE PTR [rax]
So everything works out fine in the end, I just don't understand exactly how it happens.
Related
While answering another question on Stack, I came upon a question myself. With the following code in shared.c:
#include "stdio.h"
int glob_var = 0;
void shared_func(){
printf("Hello World!");
glob_var = 3;
}
Compiled with:
gcc -shared -fPIC shared.c -oshared.so
If I disassemble the code with objdump -D shared.so, I get the following for shared_func:
0000000000001119 <shared_func>:
1119: f3 0f 1e fa endbr64
111d: 55 push %rbp
111e: 48 89 e5 mov %rsp,%rbp
1121: 48 8d 3d d8 0e 00 00 lea 0xed8(%rip),%rdi # 2000 <_fini+0xebc>
1128: b8 00 00 00 00 mov $0x0,%eax
112d: e8 1e ff ff ff callq 1050 <printf#plt>
1132: 48 8b 05 9f 2e 00 00 mov 0x2e9f(%rip),%rax # 3fd8 <glob_var##Base-0x54>
1139: c7 00 03 00 00 00 movl $0x3,(%rax)
113f: 90 nop
1140: 5d pop %rbp
1141: c3 retq
Using readelf -S shared.so, I get (for the GOT):
[22] .got PROGBITS 0000000000003fd8 00002fd8
0000000000000028 0000000000000008 WA 0 0 8
Correct me if I'm wrong but, looking at this, the relocation for accessing glob_var seems to be through the GOT. As I read on some websites, this is due to limitations in x86-64 machine code where RIP-relative addressing is limited to a 32 bits offset. This explanation is not satisfactory to me because, since the global variable is in the same shared object, then it is guaranteed to be found in its own data segment. The global variable could thus be accessed using RIP-relative addressing without an issue.
I would understand the GOT relocation if glob_var had been declared extern but, in this case, it is defined in the same shared object. Why does gcc require a relocation through the GOT? Is it because it is not able to detect that the global variable is defined within the same shared object?
Related: Why are nonstatic global variables defined in shared objects referenced using GOT?
The above is 11 years old and doesn't answer my question because there doesn't seem to be an appropriate answer there.
Bonus: what does <glob_var##Base-0x54> mean in the disassembly of shared_func? Why it isn't <glob_var#GOT>?
Thanks for any help!
so I've started learning about machine language today. I wrote a basic "Hello World" program in C which prints "Hello, world!" ten times using a for loop. I then used the Gnu Debugger to disassemble main and look at the code in machine language (my computer has a x86 processor and I've set gdb up to use intel syntax):
user#PC:~/Path/To/Code$ gdb -q ./a.out
Reading symbols from ./a.out...done.
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 int i;
6 for(i = 0; i < 10; i++) {
7 printf("Hello, world!\n");
8 }
9 return 0;
10 }
(gdb) disassemble main
Dump of assembler code for function main:
0x0804841d <+0>: push ebp
0x0804841e <+1>: mov ebp,esp
0x08048420 <+3>: and esp,0xfffffff0
0x08048423 <+6>: sub esp,0x20
0x08048426 <+9>: mov DWORD PTR [esp+0x1c],0x0
0x0804842e <+17>: jmp 0x8048441 <main+36>
0x08048430 <+19>: mov DWORD PTR [esp],0x80484e0
0x08048437 <+26>: call 0x80482f0 <puts#plt>
0x0804843c <+31>: add DWORD PTR [esp+0x1c],0x1
0x08048441 <+36>: cmp DWORD PTR [esp+0x1c],0x9
0x08048446 <+41>: jle 0x8048430 <main+19>
0x08048448 <+43>: mov eax,0x0
0x0804844d <+48>: leave
0x0804844e <+49>: ret
End of assembler dump.
(gdb) x/s 0x80484e0
0x80484e0: "Hello, world!"
I understand most of the machine code and what each of the commands do. If I understood it correctly, the address "0x80484e0" is loaded into the esp register so that can use the memory at this address. I examined the address, and to no surprise it contained the desired string. My question now is - how did that string get there in the first place? I can't find a part in the program that sets the string up at this location.
I also don't understand something else: When I first start the program, the eip points to , where the variable i is initialized at [esp+0x1c]. However, the address that esp points to is changed later on in the program (to 0x80484e0), but [esp+0x1c] is still used for "i" after that change. Shouldn't the adress [esp+0x1c] change when the address esp points to changes?
I binary or program is made up of both machine code and data. In this case your string which you put in the source code, the compiler too that data which is just bytes, and because of how it was used was considered read only data, so depending on the compiler that might land in .rodata or .text or some other name the compiler might use. Gcc would probably call it .rodata. The program itself is in .text. The linker comes along and when it links things finds a place for .text, .data, .bss, .rodata, and any other items you may have and then connects the dots. In the case of your call to printf the linker knows where it put the string, the array of bytes, and it was told what its name was (some internal temporary name no doubt) and the printf call was told about that name to so the linker patches up the instruction to grab the address to the format string before calling printf.
Disassembly of section .text:
0000000000400430 <main>:
400430: 53 push %rbx
400431: bb 0a 00 00 00 mov $0xa,%ebx
400436: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40043d: 00 00 00
400440: bf e4 05 40 00 mov $0x4005e4,%edi
400445: e8 b6 ff ff ff callq 400400 <puts#plt>
40044a: 83 eb 01 sub $0x1,%ebx
40044d: 75 f1 jne 400440 <main+0x10>
40044f: 31 c0 xor %eax,%eax
400451: 5b pop %rbx
400452: c3 retq
400453: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40045a: 00 00 00
40045d: 0f 1f 00 nopl (%rax)
Disassembly of section .rodata:
00000000004005e0 <_IO_stdin_used>:
4005e0: 01 00 add %eax,(%rax)
4005e2: 02 00 add (%rax),%al
4005e4: 48 rex.W
4005e5: 65 6c gs insb (%dx),%es:(%rdi)
4005e7: 6c insb (%dx),%es:(%rdi)
4005e8: 6f outsl %ds:(%rsi),(%dx)
4005e9: 2c 20 sub $0x20,%al
4005eb: 77 6f ja 40065c <__GNU_EH_FRAME_HDR+0x68>
4005ed: 72 6c jb 40065b <__GNU_EH_FRAME_HDR+0x67>
4005ef: 64 21 00 and %eax,%fs:(%rax)
the compiler will have encoded this instruction but left the address as zeros probably or some fill
400440: bf e4 05 40 00 mov $0x4005e4,%edi
so that the linker could fill it in later. The gnu disassembler attempts to disassemble the .rodata (and .data, etc) blocks which doesnt make sense, so ignore the instructions it is trying to interpret your string which starts at address 0x4005e4.
Before linking a disassembly of the object shows the two sections .text and .rodata
Disassembly of section .text.startup:
0000000000000000 <main>:
0: 53 push %rbx
1: bb 0a 00 00 00 mov $0xa,%ebx
6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
d: 00 00 00
10: bf 00 00 00 00 mov $0x0,%edi
15: e8 00 00 00 00 callq 1a <main+0x1a>
1a: 83 eb 01 sub $0x1,%ebx
1d: 75 f1 jne 10 <main+0x10>
1f: 31 c0 xor %eax,%eax
21: 5b pop %rbx
22: c3 retq
0000000000000000 <.rodata.str1.1>:
0: 48 rex.W
1: 65 6c gs insb (%dx),%es:(%rdi)
3: 6c insb (%dx),%es:(%rdi)
4: 6f outsl %ds:(%rsi),(%dx)
5: 2c 20 sub $0x20,%al
7: 77 6f ja 78 <main+0x78>
9: 72 6c jb 77 <main+0x77>
b: 64 21 00 and %eax,%fs:(%rax)
unlinked it has to just pad this address/offset for the linker to fill in later.
10: bf 00 00 00 00 mov $0x0,%edi
also note the object contains only the string in .rodata. linking with libraries and other items to make it a complete program clearly added more .rodata, but the linker manages all of that.
Perhaps easier to see with this example
void more_fun ( unsigned int, unsigned int, unsigned int );
unsigned int a;
unsigned int b=5;
const unsigned int c=7;
void fun ( void )
{
more_fun(a,b,c);
}
disassembled as a object
Disassembly of section .text:
0000000000000000 <fun>:
0: 8b 35 00 00 00 00 mov 0x0(%rip),%esi # 6 <fun+0x6>
6: 8b 3d 00 00 00 00 mov 0x0(%rip),%edi # c <fun+0xc>
c: ba 07 00 00 00 mov $0x7,%edx
11: e9 00 00 00 00 jmpq 16 <fun+0x16>
Disassembly of section .data:
0000000000000000 <b>:
0: 05 .byte 0x5
1: 00 00 add %al,(%rax)
...
Disassembly of section .rodata:
0000000000000000 <c>:
0: 07 (bad)
1: 00 00 add %al,(%rax)
...
and for whatever reason you have to link it to see the .bss section. The point of the example is the machine code for the function is in .text, the uninitialized global is in .bss, the initialized global is .data and the const initialized global is .rodata. The compiler was smart enough to know that a const even if it is global wont change so it can just hardcode that value into the math and not need to read from ram, but the other two variables it has to read from ram so generates an instruction with the address zeros to be filled in by the linker at link time.
In your case your read only/const data was a collection of bytes and it wasnt a math operation so the bytes as defined in your source file were placed in memory so they could be pointed at as the first parameter to printf.
There is more to a binary than just machine code. And the compiler and linker can have things placed in memory for the machine code to get, the machine code itself does not have to write every value that will be used by the rest of the machine code.
The compiler 'hard wires' the string into the object code and the linker then 'hard wires' it into the machine code.
Not that the string is embedded into the code, and not stored in a data area meaning that if you took a pointer to the string and attempted to change it you would get an exception.
i am write a mini os. And when i write this code to show time clock, its goes wrong
7 void timer_callback(pt_regs *regs)
8 {
9 static uint32_t tick = 0;
10 printf("Tick: %dtimes\n", tick);
11 tick++;
12 }
tick is initialise not with 0, but 1818389861. but if tick init with 0x01 or anything else zero, it's ok!!!
so i wirte a simple c file then objdump:
staic.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
extern void printf(char *, int);
int main(){
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
static int a = 1;
printf("%d\n", a);
11: a1 00 00 00 00 mov 0x0,%eax
16: 83 ec 08 sub $0x8,%esp
19: 50 push %eax
1a: 68 00 00 00 00 push $0x0
1f: e8 fc ff ff ff call 20 <main+0x20>
24: 83 c4 10 add $0x10,%esp
return 0;
27: b8 00 00 00 00 mov $0x0,%eax
}
2c: 8b 4d fc mov -0x4(%ebp),%ecx
2f: c9 leave
30: 8d 61 fc lea -0x4(%ecx),%esp
33: c3 ret
so strange, no memory used!!!
Update: let me say it clearly
the second static.c is an experiment, it was thought it show no memory used, but i was wrong, mov 0x0 %eab is. i confuse 0x0 and $0x0 /..\
my origin problem is why tick not succeed init with 0.(but can init with 1 or anyelsenumber).
i look up it again use gdb, ok, it do use memory like mov
eax,ds:0x106010,but the real strong thing is the memory x 0x106010 is not 0,but it should be, just as i said, if i let tick = 1 or anythingelse, memory do init as i want, that is the strange thing!
the tool: gdb ,objdump return different asm(different means,not formate),because, just learn os,not good at c, so i let it go,ignore it....
Memory is used, be sure of that; however, you won't find that memory in the .text section. Memory for static variables is allocated in either .bss (when zero-initialized; or, in case of C++, dynamically initialized) or .data (when non-zero initialized) section.
When dumping object files with objdump using the -d (disassembly) option, it is important to also use the -r (relocations) option. Without that, the disassembly you get is deceiving and makes little sense.
In your case, the instruction at addresses 11 and 1f must have relocations, at address 11, to the variable a and at address 1f, to the function printf. The instruction at address 11 loads the value from your variable a, without proper relocations it looks as if it loaded a value from address 0.
As to your original question, the value you get, 1818389861, or 0x6C626D65, is quite remarkable. I would bet that somewhere in your program you have a buffer overrun involving a string containing the subsequence embl.
As a side note, I would like to call your attention to the use of correct type specifications in printf calls. The type specification %d corresponds to the type int; on all modern mainstream architectures, int and int32_t are of the same size. However, that is not guaranteed to always be so. There are special type specifications for use with explicitly-sized types, for example, for an int32_t you use "PRId32":
uint32_t x;
printf("%"PRId32, x);
Where are static local variables stored in memory? Local variables can be accessed only inside the function in which they are declared.
Global static variables go into the .data segment.
If both the name of the static global and static local variable are same, how does the compiler distinguish them?
Static variables go into the same segment as global variables. The only thing that's different between the two is that the compiler "hides" all static variables from the linker: only the names of extern (global) variables get exposed. That is how compilers allow static variables with the same name to exist in different translation units. Names of static variables remain known during the compilation phase, but then their data is placed into the .data segment anonymously.
Static variable is almost similar to global variable and hence the uninitialized static variable is in BSS and the initialized static variable is in data segment.
As mentioned by dasblinken, GCC 4.8 puts local statics on the same place as globals.
More precisely:
static int i = 0 goes on .bss
static int i = 1 goes on .data
Let's analyze one Linux x86-64 ELF example to see it ourselves:
#include <stdio.h>
int f() {
static int i = 1;
i++;
return i;
}
int main() {
printf("%d\n", f());
printf("%d\n", f());
return 0;
}
To reach conclusions, we need to understand the relocation information. If you've never touched that, consider reading this post first.
Compile it:
gcc -ggdb -c main.c
Decompile the code with:
objdump -S main.o
f contains:
int f() {
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
static int i = 1;
i++;
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
a: 83 c0 01 add $0x1,%eax
d: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 13 <f+0x13>
return i;
13: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 19 <f+0x19>
}
19: 5d pop %rbp
1a: c3 retq
Which does 3 accesses to i:
4 moves to the eax to prepare for the increment
d moves the incremented value back to memory
13 moves i to the eax for the return value. It is obviously unnecessary since eax already contains it, and -O3 is able to remove that.
So let's focus just on 4:
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
Let's look at the relocation data:
readelf -r main.o
which says how the text section addresses will be modified by the linker when it is making the executable.
It contains:
Relocation section '.rela.text' at offset 0x660 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000300000002 R_X86_64_PC32 0000000000000000 .data - 4
We look at .rela.text and not the others because we are interested in relocations of .text.
Offset 6 falls right into the instruction that starts at byte 4:
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
^^
This is offset 6
From our knowledge of x86-64 instruction encoding:
8b 05 is the mov part
00 00 00 00 is the address part, which starts at byte 6
AMD64 System V ABI Update tells us that R_X86_64_PC32 acts on 4 bytes (00 00 00 00) and calculates the address as:
S + A - P
which means:
S: the segment pointed to: .data
A: the Added: -4
P: the address of byte 6 when loaded
-P is needed because GCC used RIP relative addressing, so we must discount the position in .text
-4 is needed because RIP points to the following instruction at byte 0xA but P is byte 0x6, so we need to discount 4.
Conclusion: after linking it will point to the first byte of the .data segment.
to understand the concept of relocation, i wrote a simple chk.c program as follows :
1 #include<stdio.h>
2 main(){
3 int x,y,sum;
4 x = 3;
5 y = 4;
6 sum = x + y;
7 printf("sum = %d\n",sum);
8 }
its equivalent assembly code, using "objdump -d chk.o" is :
00000000 <main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 20 sub $0x20,%esp
9: c7 44 24 1c 03 00 00 movl $0x3,0x1c(%esp)
10: 00
11: c7 44 24 18 04 00 00 movl $0x4,0x18(%esp)
18: 00
19: 8b 44 24 18 mov 0x18(%esp),%eax
1d: 8b 54 24 1c mov 0x1c(%esp),%edx
21: 8d 04 02 lea (%edx,%eax,1),%eax
24: 89 44 24 14 mov %eax,0x14(%esp)
28: b8 00 00 00 00 mov $0x0,%eax
2d: 8b 54 24 14 mov 0x14(%esp),%edx
31: 89 54 24 04 mov %edx,0x4(%esp)
35: 89 04 24 mov %eax,(%esp)
38: e8 fc ff ff ff call 39 <main+0x39>
3d: c9 leave
3e: c3 ret
and .rel.text section seen using readelf is as follows :
Relocation section '.rel.text' at offset 0x360 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000029 00000501 R_386_32 00000000 .rodata
00000039 00000902 R_386_PC32 00000000 printf
i have following questions based on this :
1) from 2nd entry in .rel.text section, i am able to understand that value at offset 0x39 in .text section (which is 0xfcffffff here) has to be replaced with address of a symbol associated with index 9 of symbol table (& that comes out to be printf). But i am not able to clearly understand the meaning of 0x02 (ELF32_R_TYPE) here. What does R_386_PC32 specify here ? Can anyone please explain its meaning clearly.
2) i am also not able to understand the 1st entry. what needs to be replaced at offset of 0x29 in .text section and why is not clear here. Again i want to know the meaning of R_386_32 here. i found one pdf elf_format.pdf, but i am not able to clearly understand the meaning of "Type" in .rel.text section from that.
3) Also i want to know the meaning of assembly inst "lea (%edx,%eax,1),%eax". Though i found a very good link (What's the purpose of the LEA instruction?) describing the meaning of lea, but the format of lea (what are 3 arg's inside brackets) is not clear.
if anyone can clearly explain the answers of above questions, it will be greatly appreciated. i am still struggling to find the answers to these questions,though i have tried a lot with google.
one more question. i have shown the symbol table entries for both offset 5 and 9 below.
Num: Value Size Type Bind Vis Ndx Name
5: 00000000 0 SECTION LOCAL DEFAULT 5
9: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf'
The info field for the first entry in .rel.text table is 0x05 which indicates the index of symbol table. I have shown the symbol table entry for index 5 above, but not able to understand how that tells us that it is for .rodata .
1), 2): R_386_32 is a relocation that places the absolute 32-bit address of the symbol into the specified memory location. R_386_PC32 is a relocation that places the PC-relative 32-bit address of the symbol into the specified memory location. R_386_32 is useful for static data, as shown here, since the compiler just loads the relocated symbol address into some register and then treats it as a pointer. R_386_PC32 is useful for function references since it can be used as an immediate argument to call. See elf_machdep.c for an example of how the relocations are processed.
3) lea (%edx,%eax,1),%eax means simply %eax = %edx + 1*%eax if expressed in C syntax. Here, it's basically being used as a substitute for the add opcode.
EDIT: Here's an example.
Suppose your code gets loaded into memory starting at 0x401000, that the string "sum = %d\n" ends up at 0x401800 (at the start of the .rodata section), and that printf is at 0x1400ab80, in libc.
Then, the R_386_32 relocation at 0x29 will place the bytes 00 18 40 00 at 0x401029 (simply copying the absolute address of the symbol), making the instruction at 0x401028
401028: b8 00 18 40 00 mov $0x401800,%eax
The R_386_PC32 relocation at 0x39 places the bytes 43 9b c0 13 at 0x401039 (the value 0x1400ab80 - 0x40103d = 0x13c09b43 in hex), making that instruction
401038: e8 43 9b c0 13 call $0x1400ab80 <printf>
We subtract 0x40103d to account for the value of %pc (which is the address of the instruction after call).
The first relocation entry is to get the pointer to your format string ("sum = ...") in the process of setting up the call to printf. Since the .rodata section is relocated as well as the .text section, references to strings and other constant data will need fixups.
With that in mind, it would appear that the R_386_32 relocations deal with data, and the R_386_PC32 with code addresses, but the ELF spec (of which I do not have a handy copy) probably explains the various details.
The lea instruction is what the compiler chose to perform the addition for this routine. It could have chosen add or a couple other possibilities, but that form of lea seems to be used quite often for certain cases, because it can combine an addition with a multiplication. The result of the instruction is lea (%edx,%eax,1),%eax is that %eax will get the value of %edx + 1 * %eax. The 1 can be replaced with a restricted set of small integers. The original purpose of the lea instruction was "Load Effective Address" - to take a base pointer, an index, and a size and yield the address of an element in an array. But, as you can see, compilers can choose to use it for other things, as well...