I work on making a mini debugger as a personal project. the debugger is for x86 processors under GNU/Linux environment.
In my approach of setting a breakpoint at a specific address of the debugged program works for the first time i lunch my debugger. if i try it again it fails.
I want to make sure if i do this in a right way.
suppose i have a helloworld program to be debugged as following:
#include <iostream>
int main()
{
std::cerr << "hello,World\n";
return 0;
}
and it is dissembled with objdump -d as following
0000000000400907 <main>:
400907: 55 push %rbp
400908: 48 89 e5 mov %rsp,%rbp
40090b: be 04 0a 40 00 mov $0x400a04,%esi
400910: bf 60 10 60 00 mov $0x601060,%edi
400915: e8 06 ff ff ff callq 400820 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
40091a: b8 00 00 00 00 mov $0x0,%eax
40091f: 5d pop %rbp
400920: c3 retq
and for sure i compiled my helloworld with -g option.
for the first trial, i was setting a breakpoint at 0x400915 and at 0x40091a . so what i was expecting is that when i continue(the command like GDB) my debugger, it will stop at 0x400915 and when i continue again it will print "helloworld" but non of that happened. it just go to the return as there was no any breakpoints.
So as another trial, when my debugger starts my helloworld program. i see the pid of helloworld program and open its maps file at /proc/[helloworld pid]/maps which looks like
00400000-00401000 r-xp 00000000 08:07 1578540 /home/yahia/mytinydebugger/tdbg/build/debugging-examples/helloworld
00600000-00602000 rw-p 00000000 08:07 1578540 /home/yahia/mytinydebugger/tdbg/build/debugging-examples/helloworld
7f9483434000-7f9483457000 r-xp 00000000 08:06 529041 /lib/x86_64-linux-gnu/ld-2.19.so
7f9483656000-7f9483658000 rw-p 00022000 08:06 529041 /lib/x86_64-linux-gnu/ld-2.19.so
7f9483658000-7f9483659000 rw-p 00000000 00:00 0
7fff5a314000-7fff5a335000 rw-p 00000000 00:00 0 [stack]
7fff5a335000-7fff5a337000 r--p 00000000 00:00 0 [vvar]
7fff5a337000-7fff5a339000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
then focusing on the first address of the stack which is
7fff5a314000-7fff5a335000 rw-p 00000000 00:00 0 [stack
and adding 0x7fff5a314000 + 0x400915 = 0x7FFF5A714915 and the same for 0x40091a . it worked for the first time as i expected but when i tried it again it doesn't work. i even automated doing this hex math in the code by fetching it from /proc/[helloworld pid]/maps automatically. it seems it works right for the first time and whatever i try again it doesn'.
I don't know why it doesn't work always.
is my approach of setting a breakpoint by second trial is right?
I am aware that i should place INT3 instruction of intel at address i want to have the breakpoint. this is the code i use for setting the breakpoint.
void breakpoint::enable() {
// Fetch the program instruction at the desired address of a specific process.
auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr);
// Save the lower byte which will be replaced with INT3 instruction.
m_saved_data = static_cast<uint8_t>(data & 0xff);
uint64_t int3 = 0xcc;
// Set the lower byte to INT3 instruction
uint64_t data_with_int3 = ((data & ~0xff) | int3);
// Push the modified instruction with the breakpoint to the same address it was fetched.
ptrace(PTRACE_POKEDATA, m_pid, m_addr, data_with_int3);
// Enable that (this) object of the class has a breakpoint at [m_addr] of [m_pid] process.
m_enabled = true;
}
the full code of the debugger is here. it is not big. it is simple with few commits as it is in its early age.
my OS is ubuntu 14.04 and both the debugger and the helloworld example is compiled with g++ 7.2.0