To sort of answer the question you asked (rather than the problem you actually have), use @Ped7g's suggestion to "abuse" GDB's error-handling to get it to stop before the first instruction without knowing the right address:
b *0
sets a breakpoint at address zero (which is impossible).
r
runs the program, at which point GDB tries to actually set the breakpoint. It fails, and stops before any instructions are executed.
d 1
deletes the invalid breakpoint
si
(or stepi
) will single step from there.
This is a useful trick for debugging a program that has no symbols, even if it's dynamically linked using ASLR (gcc -pie
) so you can't get the real ELF entry-point address using readelf -a
.
What you did:
Then I input "break _start" and press "r" to run.
When I do that after building with the same commands you used, I get
Reading symbols from ./Mouse...(no debugging symbols found)...done.
(gdb) b _start
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (_start) pending.
I had to type y
or n
before I could enter r
to run the program.
So I don't actually have a breakpoint at the instruction labeled _start:
in the asm source. All I have is a pending breakpoint that will be set somewhere if I give GDB a symbol table. (It is possible to strip debug symbols into a separate file, so this gdb behaviour makes sense. But it unfortunately confused you in this case. :/)
Your binary is stripped because you built with ld -s
. This is the opposite of what you want for debugging. Use nasm -g -Fdwarf
to use the modern dwarf
debug-info format instead of STABS for even better symbol info.
(gdb) r
Starting program: /home/peter/src/SO/Mouse
Program received signal SIGSEGV, Segmentation fault.
0x0804806f in ?? ()
The instruction at 0x0804806f
is your int 33h
.
It's not segfaulting before _start
, it just didn't stop because you never actually set a breakpoint. In a statically-linked binary, the first instruction to run in user-space is the one at your ELF entry point. (In a dynamically linked binary, the ELF interpreter runs first and eventually jumps to your _start
, or whatever you called your entry point.)
Use layout reg
to show registers and disassembly. Use set disassembly-flavor intel
to get GNU's MASM-like syntax which is close enough to NASM to read when you know what it's supposed to be. Put these in your ~/.gdbinit
. See also the bottom of the x86 tag wiki for more debugging tips with gdb
and strace
.
As commenters have pointed out, there's no way your program can run natively under Linux. Linux doesn't natively support the BIOS int 33h
ABI, only its own system-call ABI. What are the calling conventions for UNIX & Linux system calls on i386 and x86-64. This is why int 33h
segfaults.
If you want to write MS-DOS or PC-BIOS code, use an emulator like BOCHS (which has a built-in debugger that will let you single-step anything, even a bootloader).
int 33h
from linux (that I assume this is). – Jesterglobal _start:
should beglobal _start
without the colon – Michael Petchx/i 0x0804806f
to see the problematic instruction. It should be theint 33h
. – Jester-s
option from the linker (that is removing the symbols) – Michael Petchb *0
to set breakpoint to address zero, then upon "r" the gdb will try to set it up, fail, and will stop immediately (on the first instruction of the loaded file!). Delete the invalid breakpoint nowd 1
, and you canstepi
further... – Ped7g