How can I remove null bytes from my object code? - c

I want to use my own shellcode for a buffer overflow exploit so for that I have written a script in C language[shellcode script].
I have used the following commands.:
gcc -c file.c -o file.o
objdump -sS -D file.o
root#kali:~/shellcode# cat file.c
#include<stdio.h>
int main()
{
printf("Hi");
}
The above code is of 'file.c'.
I expect the output of the 'objdump -sS -D file.o' to be free from null-bytes, but actually this is my output after typing that command:
file.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 488d3d00 000000b8 00000000 UH..H.=.........
0010 e8000000 00b80000 00005dc3 ..........].
Contents of section .rodata:
0000 486900 Hi.
Contents of section .comment:
0000 00474343 3a202844 65626961 6e20382e .GCC: (Debian 8.
0010 332e302d 36292038 2e332e30 00 3.0-6) 8.3.0.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 1c000000 00410e10 8602430d .........A....C.
0030 06570c07 08000000 .W......
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <main+0xb>
b: b8 00 00 00 00 mov $0x0,%eax
10: e8 00 00 00 00 callq 15 <main+0x15>
15: b8 00 00 00 00 mov $0x0,%eax
1a: 5d pop %rbp
1b: c3 retq
Disassembly of section .rodata:
0000000000000000 <.rodata>:
0: 48 rex.W
1: 69 .byte 0x69
...
Disassembly of section .comment:
0000000000000000 <.comment>:
0: 00 47 43 add %al,0x43(%rdi)
3: 43 3a 20 rex.XB cmp (%r8),%spl
6: 28 44 65 62 sub %al,0x62(%rbp,%riz,2)
a: 69 61 6e 20 38 2e 33 imul $0x332e3820,0x6e(%rcx),%esp
11: 2e 30 2d 36 29 20 38 xor %ch,%cs:0x38202936(%rip) # 3820294e <main+0x3820294e>
18: 2e 33 2e xor %cs:(%rsi),%ebp
1b: 30 00 xor %al,(%rax)
Disassembly of section .eh_frame:
0000000000000000 <.eh_frame>:
0: 14 00 adc $0x0,%al
2: 00 00 add %al,(%rax)
4: 00 00 add %al,(%rax)
6: 00 00 add %al,(%rax)
8: 01 7a 52 add %edi,0x52(%rdx)
b: 00 01 add %al,(%rcx)
d: 78 10 js 1f <.eh_frame+0x1f>
f: 01 1b add %ebx,(%rbx)
11: 0c 07 or $0x7,%al
13: 08 90 01 00 00 1c or %dl,0x1c000001(%rax)
19: 00 00 add %al,(%rax)
1b: 00 1c 00 add %bl,(%rax,%rax,1)
1e: 00 00 add %al,(%rax)
20: 00 00 add %al,(%rax)
22: 00 00 add %al,(%rax)
24: 1c 00 sbb $0x0,%al
26: 00 00 add %al,(%rax)
28: 00 41 0e add %al,0xe(%rcx)
2b: 10 86 02 43 0d 06 adc %al,0x60d4302(%rsi)
31: 57 push %rdi
32: 0c 07 or $0x7,%al
34: 08 00 or %al,(%rax)
...
Can somebody please explain me how I can remove null-bytes from this program, or if possible write the output in assembly so that I can learn what to change and how
P.S - I know mov $0x0, $rsp can be done by xor $rsp, $rsp but I don't know about movq, lea, add, sub, etc.
Thank you for your precious time.

Removing nullbytes (\x00) from shellcode is only necessary if you are using functions that depend on a trailing \x00, such as strcpy:
char * strcpy ( char * destination, const char * source );
which copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
However strncpy copies the first num characters of source to destination, padding it with zeros until num characters have been written to destination.
char * strncpy ( char * destination, const char * source, size_t num );
This means if you pass your shellcode size/length to the parameter num, it will copy all characters into the buffer, without the hassle of removing nullbytes as they aren't terminating copying from source to destination.
To get the length of the shellcode:
#include <stdio.h>
#include <string.h>
int main()
{
char* evil="\x90\x83\xc8\xff\xf7\xd0\x50";
printf("%d",strlen(evil));
}
will return:
7

Related

dump_stack() in Linux kernel doesn't print the addresses of functions

I have a test Linux kernel module which prints the dump_stack(). But it prints an incomplete trace as it does not have the function addresses printed. I use a Ubuntu 16.04.7 LTS with 4.15.0-142-generic kernel and CONFIG_DEBUG_INFO=y is set in /boot/config-4.15.0-142-generic.
My questions:
(1) Why the function's address is not printed? I can dump the symbol table using objdump -t.
(2) Trace shows that dump_stack() is called from module_level_init . However it is module_level_init()->module_level_2()->module_level_3()
Following is the dmesg output:
[ 1347.807370] CPU: 7 PID: 13262 Comm: insmod Tainted: P OE 4.15.0-142-generic #146~16.04.1-Ubuntu
[ 1347.807371] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[29020.752090] Call Trace:
[29020.752096] dump_stack+0x6d/0x8b
[29020.752097] ? 0xffffffffc0730000
[29020.752099] module_level_init+0x1a/0x1000 [kern]
[29020.752102] do_one_initcall+0x55/0x1b0
[29020.752103] ? _cond_resched+0x1a/0x50
[29020.752105] ? kmem_cache_alloc_trace+0x165/0x1c0
[29020.752106] do_init_module+0x5f/0x222
[29020.752108] load_module+0x1894/0x1ea0
[29020.752111] ? ima_post_read_file+0x83/0xa0
[29020.752112] SYSC_finit_module+0xe5/0x120
[29020.752113] ? SYSC_finit_module+0xe5/0x120
[29020.752115] SyS_finit_module+0xe/0x10
[29020.752116] do_syscall_64+0x73/0x130
The kernel module:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
int module_level_3(void)
{
dump_stack();
return 0;
}
int module_level_2(void)
{
module_level_3();
return 0;
}
static int __init module_level_init(void)
{
printk(KERN_INFO "Hello, world\n");
module_level_2();
return 0;
}
static void __exit module_level_exit(void)
{
printk(KERN_INFO "Goodbye, world\n");
}
module_init(module_level_init);
module_exit(module_level_exit);
MODULE_LICENSE("GPL");
Makefile:
obj-m += kern.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
objdump
#objdump -Sdlr kern.ko
kern.ko: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <module_level_3>:
module_level_3():
0: e8 00 00 00 00 callq 5 <module_level_3+0x5>
1: R_X86_64_PC32 __fentry__-0x4
5: 55 push %rbp
6: 48 89 e5 mov %rsp,%rbp
9: e8 00 00 00 00 callq e <module_level_3+0xe>
a: R_X86_64_PC32 dump_stack-0x4
e: 31 c0 xor %eax,%eax
10: 5d pop %rbp
11: c3 retq
12: 0f 1f 40 00 nopl 0x0(%rax)
16: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
1d: 00 00 00
0000000000000020 <module_level_2>:
module_level_2():
20: e8 00 00 00 00 callq 25 <module_level_2+0x5>
21: R_X86_64_PC32 __fentry__-0x4
25: 55 push %rbp
26: 48 89 e5 mov %rsp,%rbp
29: e8 00 00 00 00 callq 2e <module_level_2+0xe>
2a: R_X86_64_PC32 dump_stack-0x4
2e: 31 c0 xor %eax,%eax
30: 5d pop %rbp
31: c3 retq
Disassembly of section .init.text:
0000000000000000 <init_module>:
module_level_init():
0: e8 00 00 00 00 callq 5 <init_module+0x5>
1: R_X86_64_PC32 __fentry__-0x4
5: 55 push %rbp
6: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
9: R_X86_64_32S .rodata.str1.1
d: 48 89 e5 mov %rsp,%rbp
10: e8 00 00 00 00 callq 15 <init_module+0x15>
11: R_X86_64_PC32 printk-0x4
15: e8 00 00 00 00 callq 1a <init_module+0x1a>
16: R_X86_64_PC32 dump_stack-0x4
1a: 31 c0 xor %eax,%eax
1c: 5d pop %rbp
1d: c3 retq
Disassembly of section .exit.text:
0000000000000000 <cleanup_module>:
module_level_exit():
0: 55 push %rbp
1: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
4: R_X86_64_32S .rodata.str1.1+0x10
8: 48 89 e5 mov %rsp,%rbp
b: e8 00 00 00 00 callq 10 <cleanup_module+0x10>
c: R_X86_64_PC32 printk-0x4
10: 5d pop %rbp
11: c3 retq

'if' test condition in c - does it evaluate?

When calling a function in the test portion of an if statement in c, does it evaluate exactly as if you had called it normally? As in, will all the effects besides the return value evaluate and persist?
For example, if I want to include an error check when calling fseek, can I write
if( fseek(file, 0, SEEK_END) ) {fprintf(stderr, "File too long")};
and be functionally the same as:
long int i = fseek(file, 0, SEEK_END);
if( i ) {fprintf(stderr, "File too long")};
?
https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#The-if-Statement
https://www.gnu.org/software/libc/manual/html_node/File-Positioning.html
Yes, this is exactly the same. This only difference is you won't be able to use again the result of the operation executed in the if statement.
In both cases the operation is being executed BEFORE the condition (comparison) happens. To illustrate this, we can see what is the result of the two different cases in machine code. Please do note that the output machine code will vary depending of the OS and compiler.
Source file 'a.c':
#include <stdio.h>
int
main(void)
{
FILE *f = fopen("testfile", "r");
long int i = fseek(f, 0, SEEK_END);
if (i)
fprintf(stderr, "Error\n");
return 0;
}
$ gcc -O1 a.c -o a
Source file 'b.c':
#include <stdio.h>
int
main(void)
{
FILE *f = fopen("testfile", "r");
if (fseek(f, 0, SEEK_END))
fprintf(stderr, "Error\n");
return 0;
}
$ gcc -O1 b.c -o b
You will note that for both cases I used the option '-O1' which allows the compiler to introduce small optimizations, this is mostly to make the machine code a little cleaner as without optimization the compiler converts "literally" to machine code.
$ objdump -Mintel -D a |grep -i main -A20
0000000000001189 <main>:
1189: f3 0f 1e fa endbr64
118d: 48 83 ec 08 sub rsp,0x8
1191: 48 8d 35 6c 0e 00 00 lea rsi,[rip+0xe6c] # 2004 <_IO_stdin_used+0x4>
1198: 48 8d 3d 67 0e 00 00 lea rdi,[rip+0xe67] # 2006 <_IO_stdin_used+0x6>
119f: e8 dc fe ff ff call 1080 <fopen#plt>
# Interesting part
11a4: 48 89 c7 mov rdi,rax # Sets return of fopen as param 1
11a7: ba 02 00 00 00 mov edx,0x2 # Sets Ox2 (SEEK_END) as param 3
11ac: be 00 00 00 00 mov esi,0x0 # Sets 0 as param 2
11b1: e8 ba fe ff ff call 1070 <fseek#plt> # Call to FSEEK being made and stored in register
11b6: 85 c0 test eax,eax # Comparison being made
11b8: 75 0a jne 11c4 <main+0x3b> # Comparison jumping
# End of interesting part
11ba: b8 00 00 00 00 mov eax,0x0
11bf: 48 83 c4 08 add rsp,0x8
11c3: c3 ret
11c4: 48 8b 0d 55 2e 00 00 mov rcx,QWORD PTR [rip+0x2e55] # 4020 <stderr##GLIBC_2.2.5>
11cb: ba 06 00 00 00 mov edx,0x6
11d0: be 01 00 00 00 mov esi,0x1
11d5: 48 8d 3d 33 0e 00 00 lea rdi,[rip+0xe33] # 200f <_IO_stdin_used+0xf>
11dc: e8 af fe ff ff call 1090 <fwrite#plt>
11e1: eb d7 jmp 11ba <main+0x31>
11e3: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
11ea: 00 00 00
11ed: 0f 1f 00 nop DWORD PTR [rax]
Objdumping on binary 'b' yields an almost identical same machine code result. To sum it up, whatever you put in your if statement is evaluated and will yield a beind-the-scene equivalent result whether or not you assign it a variable first.
Edit:
For reference, this is the output of $ objdump -Mintel -D b |grep -i main -A20:
0000000000001189 <main>:
1189: f3 0f 1e fa endbr64
118d: 48 83 ec 08 sub rsp,0x8
1191: 48 8d 35 6c 0e 00 00 lea rsi,[rip+0xe6c] # 2004 <_IO_stdin_used+0x4>
1198: 48 8d 3d 67 0e 00 00 lea rdi,[rip+0xe67] # 2006 <_IO_stdin_used+0x6>
119f: e8 dc fe ff ff call 1080 <fopen#plt>
# Interesting Part
11a4: 48 89 c7 mov rdi,rax
11a7: ba 02 00 00 00 mov edx,0x2
11ac: be 00 00 00 00 mov esi,0x0
11b1: e8 ba fe ff ff call 1070 <fseek#plt>
11b6: 85 c0 test eax,eax
11b8: 75 0a jne 11c4 <main+0x3b>
# End of interesting part
11ba: b8 00 00 00 00 mov eax,0x0
11bf: 48 83 c4 08 add rsp,0x8
11c3: c3 ret
11c4: 48 8b 0d 55 2e 00 00 mov rcx,QWORD PTR [rip+0x2e55] # 4020 <stderr##GLIBC_2.2.5>
11cb: ba 06 00 00 00 mov edx,0x6
11d0: be 01 00 00 00 mov esi,0x1
11d5: 48 8d 3d 33 0e 00 00 lea rdi,[rip+0xe33] # 200f <_IO_stdin_used+0xf>
11dc: e8 af fe ff ff call 1090 <fwrite#plt>
11e1: eb d7 jmp 11ba <main+0x31>
11e3: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
11ea: 00 00 00
11ed: 0f 1f 00 nop DWORD PTR [rax]
The short answer is yes (as in your trivial example), the long answer is maybe.
When the logical expression (any) is more complex the C language evaluates it until the result of the whole expression is fully determined. The remaining operations are not evaluated.
Examples:
int x = 0;
if(x && foo()) {}
the foo will not be called because x is false - and then the whole operation is false.
int x = 1;
if(x && foo()) {}
the foo will be called because x is true and the second part of the expression is needed to get the result.
It is called Short circuit evaluation and all logical expressions in C are evaluated this way.

Why does global symbol in the same file needed to be relocated?

I had a C program for test: a.c
int a = 0;
static int fa_local()
{
a = 78;
int b;
int c;
}
int fa_global()
{
a = 7777;
fa_local();
}
int test()
{
a = 6666;
fa_global();
}
This is its relocation table after build:
[freeman#centos-7 link_symbol_test]$ gcc -c a.c
[freeman#centos-7 link_symbol_test]$ readelf -r a.o
Relocation section '.rela.text' at offset 0x5d0 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000900000002 R_X86_64_PC32 0000000000000000 a - 8
000000000016 000900000002 R_X86_64_PC32 0000000000000000 a - 8
000000000030 000900000002 R_X86_64_PC32 0000000000000000 a - 8
00000000003e 000a00000002 R_X86_64_PC32 0000000000000010 fa_global - 4
the relocation entry is the funcation call fa_global() in test(), which has offset 00000000003e.
[freeman#centos-7 link_symbol_test]$ objdump -dS a.o:
0000000000000010 <fa_global>:
int fa_global()
{
10: 55 push %rbp
11: 48 89 e5 mov %rsp,%rbp
a = 7777;
14: c7 05 00 00 00 00 61 movl $0x1e61,0x0(%rip) # 1e <fa_global+0xe>
1b: 1e 00 00
fa_local();
1e: b8 00 00 00 00 mov $0x0,%eax
23: e8 d8 ff ff ff callq 0 <fa_local>
}
28: 5d pop %rbp
29: c3 retq
000000000000002a <test>:
int test()
{
2a: 55 push %rbp
2b: 48 89 e5 mov %rsp,%rbp
a = 6666;
2e: c7 05 00 00 00 00 0a movl $0x1a0a,0x0(%rip) # 38 <test+0xe>
35: 1a 00 00
fa_global();
38: b8 00 00 00 00 mov $0x0,%eax
3d: e8 00 00 00 00 callq 42 <test+0x18>
}
42: 5d pop %rbp
43: c3 retq
For fa_global(), it is in the same file.
why would this symbol need to be relocated,
while static symbol fa_local() doesn't?
2017.9.12 update: assembly code after optimization
[freeman#centos-7 relocation_test]$ gcc -fno-inline -O2 -c a.c
[freeman#centos-7 relocation_test]$ objdump -dS a.o
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <fa_local>:
0: c7 05 00 00 00 00 4e movl $0x4e,0x0(%rip) # a <fa_local+0xa>
7: 00 00 00
a: c3 retq
b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000000010 <fa_global>:
10: 31 c0 xor %eax,%eax
12: c7 05 00 00 00 00 61 movl $0x1e61,0x0(%rip) # 1c <fa_global+0xc>
19: 1e 00 00
1c: eb e2 jmp 0 <fa_local>
1e: 66 90 xchg %ax,%ax
0000000000000020 <test>:
20: 31 c0 xor %eax,%eax
22: c7 05 00 00 00 00 0a movl $0x1a0a,0x0(%rip) # 2c <test+0xc>
29: 1a 00 00
2c: e9 00 00 00 00 jmpq 31 <test+0x11>
But I still see the relocation entry:
000000000000002d R_X86_64_PC32 fa_global-0x0000000000000004
fa_local is a function. The compiler can determine its offset from the calling point. it uses a PC relative addressing mode for the call instruction so it does not need the absolute address and can emit the code directly.
Conversely, the a symbol is in a different section of memory, a writable segment whose offset cannot be determined at compile time. The linker does this in the relocation phase.
Right here the function is in the same file, but being non-static it can also be called from other files compiled later.
The compiler cannot know if that will happen, so it has to "prepare for the worst."

How does a compiled "Hello World" C program store the String using machine language?

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.

When static variables are created in c language

Here gdb does not stop at Line:4.
Next,
Without hitting the declaration line at Line:5, variable x is existing and initialized.
Next,
But here it shows out of scope (yes it should according to me).
Now, I have the following doubts regarding this particular instance of c program.
When exactly the memory for variable x in P1() gets created and initialized?
why gdb did not stop at static declaration statement in inside P1() in the first example?
If we call P1() again will the program control simply skip the declaration statement?
It has already been explained (in related topics linked in comments below question) how static variables work.
Here is actual code generated by a gcc for your p1 function (by gcc -c -O0 -fomit-frame-pointer -g3 staticvar.c -o staticvar.o) then disassembled with related source.
Disassembly of section .text:
0000000000000000 <p1>:
#include <stdio.h>
void p1(void)
{
0: 48 83 ec 08 sub $0x8,%rsp
static int x = 10;
x += 5;
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <p1+0xa>
a: 83 c0 05 add $0x5,%eax
d: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 13 <p1+0x13>
printf("%d\n", x);
13: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 19 <p1+0x19>
19: 89 c6 mov %eax,%esi
1b: bf 00 00 00 00 mov $0x0,%edi
20: b8 00 00 00 00 mov $0x0,%eax
25: e8 00 00 00 00 callq 2a <p1+0x2a>
}
2a: 90 nop
2b: 48 83 c4 08 add $0x8,%rsp
2f: c3 retq
So, as you see there is no code for declaration of x. GDB can only break on actual machine code instruction and as there is none, it breaks on next instruction (mov), which matches line 5.

Resources