操作系统为了安全起见,会有地址空间随机化(Address space layout randomization),默认是开启的。开启 ASLR,在每次程序运行时的时候,装载的可执行文件和共享库都会被映射到虚拟地址空间的不同地址处;而关掉 ASLR,则可以保证每次运行时都会被映射到虚拟地址空间的相同地址处。例如一个程序中调用malloc分配内存,每次运行程序时得到的内存地址是一样的。
调试器如gdb为了调试方便,默认是关闭aslr的,可以使用set disable-randomization off将aslr打开。
(gdb) show disable-randomization
Disabling randomization of debuggee's virtual address space is on.
# 下面这种方式不行,因为会找不到arg
b function if arg cond
# 以下几种方式都可以
# 1. 在具体行号上打断点
b line_number if arg
# 2. 根据处理器的abi,用寄存器来代替参数,例如如果function只有一
# 个参数arg的话,那么该参数会放置到rsi中
b function if $rsi ...
# 3. 使用command命令
b function
command
if arg !cond
continue
end
# 在第N个断点处,加上条件cond,cond满足时断点才停止
# 其中的cond可以通过"||"或者"&&"连接
condition N cond
watch *addr cond
# 如果addr处的值被设置为xxx,则停下来
watch *addr if *addr == xxx
# 方式一:在breakpoint后执行commands
break func:12
command
...
end
# 方式二,指定第N个断点触发后执行命令
commands N
...
end
# 在满足REGEXP正则表达式的函数上打断点
rbeak REGEXP
# 常用场景:不熟悉工程时,可以通过rbreak统一在所有名字相似的函数
# 上打上断点,观察哪些函数会被执行,从而理解函数功能与方便调试
# 例:弄清-fdump-tree-all选项调用了哪些打印函数
rb ^dump_.*function*
rb ^func*
rb func*
rb ^func
rb func[0-9]
需求:
1)程序执行到文件xxx的第yyy行时停止,则直接b xxx:yyy。
2)如果这个断点会被触发多次,即xxx:yyy会被执行多次,我们只想要在变量zzz == 10的时候停止,则重新打断点:b xxx:yyy if zzz == 10。将这个断点记作 break-n
3)如果想要在break-n被触发前的某个位置停止,例如在xxx:(yyy-100)处停止,然后查看xxx:(yyy-100)到xxx:yyy这一段代码中,程序的状态。这时我们怎么在xxx:(yyy-100)中停住呢?直接在xxx:(yyy-100)处打断点是不对的,因为我们只想查看break-n被触发的那一次,xxx:(yyy-100)位置的状态
解决方案:找到xxx:yyy被执行前一定会执行的另一位置,例如xxx:(yyy-200),在此处打上断点,记为break-m
具体步骤如下:
ignore可以用来查看函数在某段时间内(起点和终点可由自己任意设置)被调用的次数
第一步、按上述说明打上断点break-m和break-n
第二步、忽视break-m的100000000(设置得足够大即可)次断点触发:ignore break-m 100000000
第三步、接着执行程序:c
第四步、break-n被触发时,info breakpoints查看break-m被触发了多少次,即already hit x times中的x值
第五步、ignore break-m x-1
第六步、执行run,重新执行程序
第七步、程序会在break-m的第x次被触发时停住,从此处可以单步或者continue到xxx:(yyy-100),这时我们便可以查看break-n被执行前的程序状态了
# 编辑指定的源码文件
# 便于调试代码时增添注释
edit file
# 配合whatis/ptype expr使用
l function/struct
# '$'代表上一次命令的执行结果,即打印当前链表节点的下一个节点
p $.next
# 利用这种方法可以逐个遍历链表
p <enter>
# 将element地址处开始的内存当做一个数组,打印从element开始的number个数组元素,以type类型打印每个元素
p *(type *)element@number
## 显示历史命令
show commands
# 显示历史打印值
show values
需要使用-g3而不是-g,-g默认是-g2
Request debugging information and also use level to specify how much information. The default level is 2.
Level 3 includes extra information, such as all the macro definitions present in the program. Some debuggers support macro expansion when you use -g3.
info macro macro_name # 将宏扩展开后打印
macro define -- Define a new C/C++ preprocessor macro.
macro expand, macro exp -- Fully expand any C/C++ preprocessor macro invocations in EXPRESSION.
macro expand-once, macro exp1 -- Expand C/C++ preprocessor macro invocations appearing directly in EXPRESSION.
macro list -- List all the macros defined using the `macro define' command.
macro undef -- Remove the definition of the C/C++ preprocessor macro with the given name.
(gdb) help catch
Set catchpoints to catch events.
List of catch subcommands:
catch assert -- Catch failed Ada assertions, when raised.
catch catch -- Catch an exception, when caught.
catch exception -- Catch Ada exceptions, when raised.
catch exec -- Catch calls to exec.
catch fork -- Catch calls to fork.
catch handlers -- Catch Ada exceptions, when handled.
catch load -- Catch loads of shared libraries.
catch rethrow -- Catch an exception, when rethrown.
(gdb) help catch
Set catchpoints to catch events.
List of catch subcommands:
catch signal -- Catch signals by their names and/or numbers.
catch syscall -- Catch system calls by their names, groups and/or numbers.
catch throw -- Catch an exception, when thrown.
catch unload -- Catch unloads of shared libraries.
catch vfork -- Catch calls to vfork.
# catch exec/fork/syscall/vfork对于查看程序的执行过程非常有用
从链接始,即collec2到lto的处理过程
collect2
vfork->execve: ld
vfork->execve: lto-wrapper
vfork->execve: gcc
vfork->execve: lto1
lto属于ld中的一个中间过程
调用栈如下
exec_lto_wrapper
all_symbols_read_handler
plugin_call_all_symbols_read
lang_process
// lang_process:
// 1)先处理lto之前的链接过程
// 2)处理all_symbols_read_handler,执行lto优化
// 3)执行剩下的链接过程:open_input_bfds
__libc_start_call_main
# 查看进程的映射图,包含的对象,如进程中可执行程序,动态库的地址范围
# 等同于查看proc/pid/maps
info proc mappings
# 查看各个对象具体的地址映射图,即各个模块的地址范围,如
# .text/.bass/.data/heap/stack等
info files
# 检查栈帧地址属于进程的哪个对象
info proc mappings
# 查看属于所属对象的哪个模块
info files
# 根据objdump/readelf 访问该对象文件,找到栈帧对应的函数
跟踪系统调用
# -f追踪子进程
strace -o strace.log -f man ls
# 持续输出strace的结果
tail -f strace.log
常见用途:查阅程序打开的文件
案例:
watch *addr,断点失效
addr在程序运行初可能未分配内存,我们想知道addr的值何时被赋为xxx
strace -o strace.log -f ./exe
grep mmap strace.log
(gdb) b mmap if addr == ???
# /$HOME/.inputrc
$if gdb
"\C-j":"focus next\r"
"\C-k":"focus prev\r"
$endif
gdb提供python接口
增强命令行提示:GEP