GDB showing wrong address to local variables - c

The below code is an snippet from the 2nd stage loader program. It is running in Real Mode and gets called by the bootloader program. The problem is that GDB is showing wrong addresses to variables allocated in stack.
|------|------|------|
|Name |GDB |Actual|
|------|------|------|
|char a|0x7be7|0x7bfb|
|char b|0x7be6|0x7bfa|
|------|------|------|
Source:
__attribute__((noreturn))
void __main()
{
char a = 'A';
char b = 'B';
while(1);
}
Disassembly:
00008000 <__main>:
8000: 66 55 push ebp
8002: 66 89 e5 mov ebp,esp
8005: 66 83 ec 10 sub esp,0x10
8009: 67 c6 45 ff 41 mov BYTE PTR [ebp-0x1],0x41 <--- char a
800e: 67 c6 45 fe 42 mov BYTE PTR [ebp-0x2],0x42 <--- char b
8013: eb fe jmp 8013 <__main+0x13>
This file gets loaded at physical location 0x8000 by a custom bootloader.
QEMU and GDB
> qemu-system-i386 -fda build/boot.flp -s -S
> gdb loader.sym
target remote localhost:1234
set architecture i386
b __main
c
s
s
p &a
0x7be7 "" <-- Not EBP - 1
p &b
0x7be6 "" <-- Not EBP - 2
info reg
eax 0x0 0
ecx 0x0 0
edx 0x7de3 32227
ebx 0x8000 32768
esp 0x7bec 0x7bec
ebp 0x7bfc 0x7bfc <-- char a is at 0x7bfb and char b is at 0x7bfa
esi 0x0 0
edi 0x0 0
eip 0x8013 0x8013 <__main+19>
eflags 0x202 [ IF ]
cs 0x0 0
ss 0x0 0
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
I do not really understand what is going on. Could it be the -m16 option?
Compilation
gcc -std=c99 \
-nostartfiles \
-c \
-g \
-ffreestanding \
-fno-pie \
-fno-stack-protector \
-m16 \
-march=i386 \
-Wpedantic \
-Wextra \
-Wall \
-O0 bootloader/x86/phase2/loader.c -o $TEMPDIR/loader.o || exit
ld -m elf_i386 --nmagic --script=build/loader.ld $TEMPDIR/loader.o -o $TEMPDIR/loader.lo || exit
objcopy --only-keep-debug $TEMPDIR/loader.lo $SYMDIR/loader.sym||exit
objcopy -O binary $TEMPDIR/loader.lo $OBJDIR/LOADER.flt||exit
Linker Script (build/loader.ld)
ENTRY (__main)
SECTIONS
{
. = 0x8000; /* Loader is loaded at 0x0000:0x8000 */
.text :AT(0x0)
{
*.o (.text);
}
.data :
{
*.o (.data);
*.o (.bss);
*.o (.rodata);
}
/DISCARD/ :
{
*(.eh_frame)
}
}
Things I tried:
Using -m32 instead of -m16 and .code16gcc at the top of the C file.
Verified that GCC, GDB works perfectly when compilling a native application.
Used --oformat binary option in ld instead of objcopy
PS:
GCC Version: 8.3.0
GNU ld Version: 2.31.1
Linux Debian 10

Related

gdb incorrectly resolving stack variable location

While debugging a small kernel I am writing for fun/learning experience, I encountered a somewhat puzzling issue with gdb where it is apparently not correctly resolving local variable addresses on the stack. My investigation so far suggests that the debugging symbols are correct but somehow gdb still reads from a wrong memory location when displaying the contents of that variable.
The relevant C code in question is:
typedef union
{
uint16_t packed;
struct __attribute__((packed))
{
uint8_t PhysicalLimit;
uint8_t LinearLimit;
} limits;
} MemAddrLimits;
void KernelMain32()
{
ClearScreen();
SimplePrint("kernelMain32");
MemAddrLimits memAddr;
memAddr.packed = GetMemoryAddressLimits();
for (;;) {}
}
where GetMemoryAddressLimits() returns the memory address widths provided by the cpuid instruction in a 2-byte integer (0x3028 currently for my tests). However, when stepping through this function using gdb to print the value of memAddr does not show the right result:
gdb> p memAddr
$1 = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}
gdb> info locals
memAddr = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}
gdb> info addr memAddr prints Symbol "memAddr" is a variable at frame base reg $ebp offset 8+-18. i.e., memAddr is located at ebp-10 and, indeed, inspecting that address shows the expected content:
gdb> x/hx $ebp-10
0x8ffee: 0x3028
In contrast gdb> p &memAddr gives a value of (MemAddrLimits *) 0x7f6 at which location the memory is zeroed.
When declaring memAddr as a uint16_t instead of my union type these issues do not occur. In that case we get
gdb> info addr memAddr
Symbol "memAddr" is multi-location:
Range 0x8b95-0x8b97: a variable in $eax
.
However, the result is still (also) written to ebp-10, i.e., the disassembly of the function is identical - the only difference is in debug symbols.
Am I missing something here or has someone a good idea on what might be going wrong in this case?
More Details
Program versions and build flags
Using gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0 and GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1.
Compiling with flags
-ffreestanding -m32 -fcf-protection=none -fno-pie -fno-pic -O0 -gdwarf-2 -fvar-tracking -fvar-tracking-assignments
and linking with
-m elf_i386 -nodefaultlibs -nostartfiles -Ttext 0x7c00 -e start -g
The linking phase produces the kernel.elf which I postprocess to extract the raw executable binary as well as a symbols file to load into gdb. So far, this has been working well for me.
There's obviously more code involved in the binary than what I have shown, most of which written in assembly, which shouldn't be relevant here.
Compiled Files
gcc generates the following code (snippet from objdump -d kernel.elf):
00008b74 <KernelMain32>:
8b74: 55 push ebp
8b75: 89 e5 mov ebp,esp
8b77: 83 ec 18 sub esp,0x18
8b7a: e8 f0 fe ff ff call 8a6f <ClearScreen>
8b7f: 68 41 8c 00 00 push 0x8c41
8b84: e8 7a ff ff ff call 8b03 <SimplePrint>
8b89: 83 c4 04 add esp,0x4
8b8c: e8 0f 00 00 00 call 8ba0 <GetMemoryAddressLimits>
8b91: 66 89 45 f6 mov WORD PTR [ebp-0xa],ax
8b95: eb fe jmp 8b95 <KernelMain32+0x21>
From that we can see that memAddr is indeed located at ebp-10 on the stack, consistent to what gdb> info addr memAddr told us.
Dwarf information (objdump --dwarf kernel.elf):
<1><4ff>: Abbrev Number: 20 (DW_TAG_subprogram)
<500> DW_AT_external : 1
<501> DW_AT_name : (indirect string, offset: 0x23c): KernelMain32
<505> DW_AT_decl_file : 2
<506> DW_AT_decl_line : 79
<507> DW_AT_decl_column : 6
<508> DW_AT_low_pc : 0x8b74
<50c> DW_AT_high_pc : 0x8b97
<510> DW_AT_frame_base : 0x20 (location list)
<514> DW_AT_GNU_all_call_sites: 1
<515> DW_AT_sibling : <0x544>
<2><519>: Abbrev Number: 21 (DW_TAG_variable)
<51a> DW_AT_name : (indirect string, offset: 0x2d6): memAddr
<51e> DW_AT_decl_file : 2
<51f> DW_AT_decl_line : 86
<520> DW_AT_decl_column : 19
<521> DW_AT_type : <0x4f3>
<525> DW_AT_location : 2 byte block: 91 6e (DW_OP_fbreg: -18)
and relevant snippet from objdump --dwarf=loc kernel.elf:
Offset Begin End Expression
00000000 <End of list>
objdump: Warning: There is an overlap [0x8 - 0x0] in .debug_loc section.
00000000 <End of list>
objdump: Warning: There is a hole [0x8 - 0x20] in .debug_loc section.
00000020 00008b74 00008b75 (DW_OP_breg4 (esp): 4)
0000002c 00008b75 00008b77 (DW_OP_breg4 (esp): 8)
00000038 00008b77 00008b97 (DW_OP_breg5 (ebp): 8)
00000044 <End of list>
[...]
These all seem to be what I'd expect. (I'm not sure if the warnings in the last one have significance, though).
Additional Note
If I change compilation flag -gdwarf-2 to just -g I get
gdb> p &memAddr
$1 = (MemAddrLimits *) 0x8ffde
gdb> info addr memAddr
Symbol "memAddr" is a complex DWARF expression:
0: DW_OP_fbreg -18
.
gdb> p memAddr
$2 = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}
gdb> p/x $ebp-10
$3 = 0x8ffee
So memAddr is still not resolved correctly but p &memAddr at least is in the stack frame and not somewhere completely different. However, info addr memAddr seems to have problems now...
After some more investigation, I have tracked this to being due to remote debugging 32-bit code (my kernel not yet having switched to long mode) on a x86-64 qemu emulated system.
If I debug the same code with qemu-system-i386 everything works just as it should.

Again - can't change global variable in C

I'm newbie to C, and this question could be asked for 100 times, but I can't find the same case.
Here's the code:
int r;
void set_idt() {
r = 0x88;
if(r == 0){
kprint_hex_byte(0xff);
}
}
It's strange, but I can't modify the global variable and kprint_hex_byte gets executed. When I try to pick &r inside set_idt() I get the valid address, but assignment of the variable doesn't work!
UPDATE
I decided to provide a minimal example
Here's the minified code:
int r = 0x34def;
void main(){
r = 0x33;
char d = 0;
if(r == 0){
d = 1;
}
else{
d = 2;
}
}
I try to compile it and disassemble it using the following commands:
gcc -ffreestanding -fno-asynchronous-unwind-tables -fno-pie -m32 -c kernel.c -o kernel.o
ld -o kernel.bin -m elf_i386 --oformat binary kernel.o
ndisasm -b 32 kernel.bin > kernel.dis
So as a result of this operations I don't have any hint that any variable r or address space is initialized as 0x34def.
Here's the .dis code:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 83EC10 sub esp,byte +0x10
00000006 C7052C9004083300 mov dword [dword 0x804902c],0x33
-0000
00000010 C645FF00 mov byte [ebp-0x1],0x0
00000014 A12C900408 mov eax,[0x804902c]
00000019 85C0 test eax,eax
0000001B 7506 jnz 0x23
0000001D C645FF01 mov byte [ebp-0x1],0x1
00000021 EB04 jmp short 0x27
00000023 C645FF02 mov byte [ebp-0x1],0x2
00000027 90 nop
00000028 C9 leave
00000029 C3 ret
PS
In the original example when I tried to disassemble it, I also had some assembler instructions dealing with expected memory address and commands test and jnz were presented the exact same way, but it didn't work as expected

Remove stack frame setup/initilization

I have the following program:
void main1() {
((void(*)(void)) (0xabcdefabcdef)) ();
}
I create it with the following commands:
clang -fno-stack-protector -c -static -nostdlib -fpic -fpie -O0 -fno-asynchronous-unwind-tables main.c -o shellcode.o
ld shellcode.o -o shellcode -S -static -dylib -e main1 -order_file order.txt
gobjcopy -O binary --only-section=.text shellcode shellcode.output
The assembly looks like the following:
//
// ram
// ram: 00000000-00000011
//
**************************************************************
* FUNCTION *
**************************************************************
undefined FUN_00000000()
undefined AL:1 <RETURN>
FUN_00000000
00000000 55 PUSH RBP
00000001 48 89 e5 MOV RBP,RSP
00000004 48 b8 ef MOV RAX,0xabcdefabcdef
cd ab ef
cd ab 00 00
0000000e ff d0 CALL RAX
00000010 5d POP RBP
00000011 c3 RET
How do I get clang to remove the PUSH RBP, MOV RBP,RSP and POP RBP instructions as they are unnecessary?
I can do this if I write the program in assembly with the following lines:
.globl start
start:
movq $0xabcdefabcdef, %rax
call *%rax
ret
and with the following build commands:
clang -static -nostdlib main.S -o crashme.o
gobjcopy -O binary --only-section=.text crashme.o crashme.output
and the resulting assembly:
//
// ram
// ram: 00000000-0000000c
//
assume DF = 0x0 (Default)
00000000 48 b8 ef MOV RAX,0xabcdefabcdef
cd ab ef
cd ab 00 00
0000000a ff d0 CALL RAX
0000000c c3 RET
but I would much rather write C code instead of assembly.
You forgot to enable optimization. Any optimization level like -O3 enables -fomit-frame-pointer.
It will also optimize the tailcall into a jmp instead of call/ret though. If you need to avoid that for some reason, maybe you can use -fomit-frame-pointer at the default -O0.
For shellcode you might want -Os to optimize for code size. Or even clang's -Oz; that will have a side-effect of avoiding some 0 bytes in the machine code by using push imm8 / pop reg to put small constants in registers, instead of mov reg, imm32.

gcc subtracting from esp before call

I am planning to use C to write a small kernel and I really don't want it to bloat with unnecessary instructions.
I have two C files which are called main.c and hello.c. I compile and link them using the following GCC command:
gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o
I am dumping .text section using following OBJDUMP command:
objdump -w -j .text -D -mi386 -Maddr16,data16,intel main.o
and get the following dump:
00001000 <main>:
1000: 67 66 8d 4c 24 04 lea ecx,[esp+0x4]
1006: 66 83 e4 f0 and esp,0xfffffff0
100a: 67 66 ff 71 fc push DWORD PTR [ecx-0x4]
100f: 66 55 push ebp
1011: 66 89 e5 mov ebp,esp
1014: 66 51 push ecx
1016: 66 83 ec 04 sub esp,0x4
101a: 66 e8 10 00 00 00 call 1030 <hello>
1020: 90 nop
1021: 66 83 c4 04 add esp,0x4
1025: 66 59 pop ecx
1027: 66 5d pop ebp
1029: 67 66 8d 61 fc lea esp,[ecx-0x4]
102e: 66 c3 ret
00001030 <hello>:
1030: 66 55 push ebp
1032: 66 89 e5 mov ebp,esp
1035: 90 nop
1036: 66 5d pop ebp
1038: 66 c3 ret
My questions are: Why are machine codes at the following lines being generated?
I can see that subtraction and addition completes each other, but why are they generated? I don't have any variable to be allocated on stack. I'd appreciate a source to read about usage of ECX.
1016: 66 83 ec 04 sub esp,0x4
1021: 66 83 c4 04 add esp,0x4
main.c
extern void hello();
void main(){
hello();
}
hello.c
void hello(){}
lscript.ld
SECTIONS{
.text 0x1000 : {*(.text)}
}
As I mentioned in my comments:
The first few lines (plus the push ecx) are to ensure the stack is aligned on a 16-byte boundary which is required by the Linux System V i386 ABI. The pop ecx and lea before the ret in main is to undo that alignment work.
#RossRidge has provided a link to another Stackoverflow answer that details this quite well.
In this case you seem to be doing real mode development. GCC isn't well suited for this but it can work and I will assume you know what you are doing. I mention some of the pitfalls of using -m16 in this Stackoverflow answer. I put this warning in that answer regarding real mode development with GCC:
There are so many pitfalls in doing this that I recommend against it.
If you remain undeterred and wish to continue forward you can do a few things to minimize the code. The 16-byte alignment of the stack at the point a function call is made is part of the more recent Linux System V i386 ABIs. Since you are generating code for a non-Linux environment you can change the stack alignment to 4 using compiler option -mpreferred-stack-boundary=2 . The GCC manual says:
-mpreferred-stack-boundary=num
Attempt to keep the stack boundary aligned to a 2 raised to num byte boundary. If -mpreferred-stack-boundary is not specified, the default is 4 (16 bytes or 128 bits).
If we add that to your GCC command we get gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o -mpreferred-stack-boundary=2:
00001000 <main>:
1000: 66 55 push ebp
1002: 66 89 e5 mov ebp,esp
1005: 66 e8 04 00 00 00 call 100f <hello>
100b: 66 5d pop ebp
100d: 66 c3 ret
0000100f <hello>:
100f: 66 55 push ebp
1011: 66 89 e5 mov ebp,esp
1014: 66 5d pop ebp
1016: 66 c3 ret
Now all the extra alignment code to get it on a 16-byte boundary has disappeared. We are left with typical function frame pointer prologue and epilogue code. This is often in the form of push ebp and mov ebp,esp pop ebp. we can remove these with the -fomit-frame-pointer define in the GCC manual as:
The option -fomit-frame-pointer removes the frame pointer for all functions which might make debugging harder.
If we add that option we get gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o -mpreferred-stack-boundary=2 -fomit-frame-pointer:
00001000 <main>:
1000: 66 e8 02 00 00 00 call 1008 <hello>
1006: 66 c3 ret
00001008 <hello>:
1008: 66 c3 ret
You can then optimize for size with -Os. The GCC manual says this:
-Os
Optimize for size. -Os enables all -O2 optimizations that do not typically increase code size. It also performs further optimizations designed to reduce code size.
This has a side effect that main will be placed into a section called .text.startup. If we display both with objdump -w -j .text -j .text.startup -D -mi386 -Maddr16,data16,intel main.o we get:
Disassembly of section .text:
00001000 <hello>:
1000: 66 c3 ret
Disassembly of section .text.startup:
00001002 <main>:
1002: e9 fb ff jmp 1000 <hello>
If you have functions in separate objects you can alter the calling convention so the first 3 Integer class parameters are passed in registers rather than the stack. The Linux kernel uses this method as well. Information on this can be found in the GCC documentation:
regparm (number)
On the Intel 386, the regparm attribute causes the compiler to pass arguments number one to number if they are of integral type in registers EAX, EDX, and ECX instead of on the stack. Functions that take a variable number of arguments will continue to be passed all of their arguments on the stack.
I wrote a Stackoverflow answer with code that uses __attribute__((regparm(3))) that may be a useful source of further information.
Other Suggestions
I recommend you consider compiling each object individually rather than altogether. This is also advantageous since it can be more easily be done in a Makefile later on.
If we look at your command line with the extra options mentioned above you'd have:
gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o \
-mpreferred-stack-boundary=2 -fomit-frame-pointer -Os
I recommend you do it this way:
gcc -c -Os -Wall -m16 -ffreestanding -nostdlib -mpreferred-stack-boundary=2 \
-fomit-frame-pointer main.c -o main.o
gcc -c -Os -Wall -m16 -ffreestanding -nostdlib -mpreferred-stack-boundary=2 \
-fomit-frame-pointer hello.c -o hello.o
The -c option (I added it to the beginning) forces the compiler to just generate the object file from the source and not to perform linking. You will also notice the -T lscript.ld has been removed. We have created .o files above. We can now use GCC to link all of them together:
gcc -ffreestanding -nostdlib -Wl,--build-id=none -m16 \
-Tlscript.ld main.o hello.o -o main.elf
The -ffreestanding will force the linker to not use the C runtime, the -Wl,--build-id=none will tell the compiler not to generate some noise in the executable for build notes. In order for this to really work you'll need a slightly more complex linker script that places the .text.startup before .text. This script also adds the .data section, the .rodata and .bss sections. The DISCARD option removes exception handling data and other unneeded information.
ENTRY(main)
SECTIONS{
.text 0x1000 : SUBALIGN(4) {
*(.text.startup);
*(.text);
}
.data : SUBALIGN(4) {
*(.data);
*(.rodata);
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss);
}
. = ALIGN(4);
__bss_end = .;
/DISCARD/ : {
*(.eh_frame);
*(.comment);
*(.note.gnu.build-id);
}
}
If we look at a complete OBJDUMP with objdump -w -D -mi386 -Maddr16,data16,intel main.elf we would see:
Disassembly of section .text:
00001000 <main>:
1000: e9 01 00 jmp 1004 <hello>
1003: 90 nop
00001004 <hello>:
1004: 66 c3 ret
If you want to convert main.elf to a binary file that you can place in a disk image and read it (ie. via BIOS interrupt 0x13), you can create it this way:
objcopy -O binary main.elf main.bin
If you dump main.bin with NDISASM using ndisasm -b16 -o 0x1000 main.bin you'd see:
00001000 E90100 jmp word 0x1004
00001003 90 nop
00001004 66C3 o32 ret
Cross Compiler
I can't stress this enough but you should consider using a GCC cross compiler. The OSDev Wiki has information on building one. It also has this to say about why:
Why do I need a Cross Compiler?
You need to use a cross-compiler unless you are developing on your own operating system. The compiler must know the correct target platform (CPU, operating system), otherwise you will run into trouble. If you use the compiler that comes with your system, then the compiler won't know it is compiling something else entirely. Some tutorials suggest using your system compiler and passing a lot of problematic options to the compiler. This will certainly give you a lot of problems in the future and the solution is build a cross-compiler.

Desiging Shellcode gives incorrect results

I made this simple assembly program:
.text
.globl _start
_start:
mov %20, %rbx
mov %1, %rax
int $0x80
This is obviously running on a 64 bit OS (Linux). I then compiled it as follows:
as -o ExitShellcode.o ExitShellcode.s
ld -o ExitShellcode ExitShellcode.o
And finally after running the program, It exits with a status of 20
echo $?
20
Using objdump to dump the shellcode for the file gives:
objdump -d ExitShellcode
ExitShellcode: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <_start>:
400078: 48 c7 c3 14 00 00 00 mov $0x14,%rbx
40007f: 48 c7 c0 01 00 00 00 mov $0x1,%rax
400086: cd 80 int $0x80
However, after putting the shellcode in this program:
#include <stdio.h>
char shellcode[] = "\x48\xc7\xc3\x14\x00\x00\x00"
"\x48\xc7\xc0\x01\x00\x00\x00"
"\xcd\x80";
int main()
{
int *ret;
ret = (int *)&ret +2;
*ret = (int)shellcode;
}
and compiling:
gcc -g -o Shellcode Shellcode.c
Shellcode.c: In function ‘main’:
Shellcode.c:13:9: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
*ret = (int)shellcode;
and running, the program exits with a 0 status:
echo $?
0
What's the proplem? Shouldn't it exit with a 20?
Your code incorrectly assumes that the compiler will put the variable ret at a certain place on the stack relative to the return address of main. Instead the compiler put it somewhere else, as it is allowed to do, and so your code does nothing. Your probably following a badly designed example you found on the Internet.
If you want to execute the "shellcode" in the shellcode array you can try casting to it a pointer to function and then calling it:
char shellcode[] = "\x48\xc7\xc3\x14\x00\x00\x00"
"\x48\xc7\xc0\x01\x00\x00\x00"
"\xcd\x80";
int main()
{
((void (*)()) shellcode)();
}
However this will still probably fail because the .data section where shellcode is placed isn't executable and so the program will crash when run. To fix that problem use the -zexecstack option when linking your program. For example:
gcc -zexecstack -g -o Shellcode Shellcode.c

Resources