手撕CSAPP(1. 计算机系统之旅)

在这里插入图片描述

1. 程序的生命周期

已HelloWorld为例,简要介绍C文件的预处理、编译、链接、执行。

#include <stdio.h>
int main()
{ 
	 printf("hello, world\n");
	 return 0;
}

转化为ASCII

#	i	n	c	l	u	d	e  SP <	 s  t   d   i	 o	.
35 105 110 99 108 117 100 101 32 60 115 116 100 105 111 46
h > \n \n i n t SP m a i n ( ) \n {
104 62 10 10 105 110 116 32 109 97 105 110 40 41 10 123
\n SP SP SP SP printf("hel
10 32 32 32 32 112 114 105 110 116 102 40 34 104 101 108
lo, SP w o r l d \ n " ) ; \n SP
108 111 44 32 119 111 114 108 100 92 110 34 41 59 10 32
SP SP SP return SP 0 ; \n } \n
32 32 32 114 101 116 117 114 110 32 48 59 10 125 10

在这里插入图片描述

main:
subq $8, %rsp
movl $.LC0, %edi
call puts
movl $0, %eax
addq $8, %rsp
ret

问题
switch 和 if-else 哪个更快?
How much overhead is incurred by a function call?
while-loop 和 for-loop 哪个更快?
How can a function run faster when we
simply rearrange the parentheses in an arithmetic expression?

2. 编程规则

头文件:声明变量和函数
C文件:做定义

#include <stdio.h>

int money = 0;

int adjust_money(int d)
{
    return money += d;
}

int main(void)
{
    while (1) {
        int x = 0;
        scanf("%d", &x);
        printf("%d\n", adjust_money(x));
        getchar();
    }
    return 0;
}

改为下面三个文件
func.h

#ifndef _FUNC_H_
#define _FUNC_H_

// int money = 0;
extern int money;
extern int adjust_money(int d);

#endif

func.c

#include "func.h"

int money = 0;

int adjust_money(int d)
{
    return money += d;
}

main.c

#include "func.h"
#include <stdio.h>

int main(void)
{
    while (1) {
        int x = 0;
        scanf("%d", &x);
        printf("%d\n", adjust_money(x));
        getchar();
    }
    return 0;
}

变量重复定义

JGR4OFNH:~/CSAPP/qw$ cc -c main.c -o main.o
JGR4OFNH:~/CSAPP/qw$ cc -c func.c -o func.o
JGR4OFNH:~/CSAPP/qw$ ls
func.c  func.h  func.o  main.c  main.o

JGR4OFNH:~/CSAPP/qw$ cc func.o main.o -o main
/usr/bin/ld: main.o:(.bss+0x0): multiple definition of `money'; func.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status
JGR4OFNH:~/CSAPP/qw$

3. GDB使用方法

测试代码

#include <stdio.h>
int minus(int a,int b){
   printf("In minus():\n"); 
   int c = a-b;
   return c;
}
int sum(int a, int b) {
    printf("In sum():\n");
    int c = a+b;
    return c;
}
void print(int xx, int *xxptr) {
  printf("In print():\n");
  printf("   xx is %d and is stored at %p.\n", xx, &xx);
  printf("   ptr points to %p which holds %d.\n", xxptr, *xxptr);
  int c = sum(2,3);
  int d = minus(3,2);
}

int main(void) {
  int x = 10;
  
  int *ptr = &x;
  printf("In main():\n");
  printf("   x is %d and is stored at %p.\n", x, &x);
  printf("   ptr points to %p which holds %d.\n", ptr, *ptr);
  
  print(x, ptr);
  return 0;
}

设置断点
可以在函数名和行号等上设置断点。程序运行后,到达断点就会自动暂停运行。此时可以查看该时刻的变量值、显示栈帧、重新设置断点或重新运行等。断点命令(break)可以简写为b。

(gdb) b main
Breakpoint 1 at 0x758: file gdb_example.c, line 9.
break 函数名
break 行号
break 文件名:行号
break 文件名:函数名
break  + 偏移量
break  - 偏移量
break  * 地址

(gdb) b print
Breakpoint 2 at 0x709: file gdb_example.c, line 4.
(gdb) b gdb_example.c:5
Breakpoint 3 at 0x715: file gdb_example.c, line 5.
(gdb) b +3
Note: breakpoint 2 also set at pc 0x709.
Breakpoint 4 at 0x709: file gdb_example.c, line 4.
(gdb) b *0x709
Note: breakpoints 2 and 4 also set at pc 0x709.
Breakpoint 5 at 0x709: file gdb_example.c, line 4.
(gdb)

设置好的断点可以通过info break 确认

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000758 in main at gdb_example.c:9
2       breakpoint     keep y   0x0000000000000709 in print at gdb_example.c:4
3       breakpoint     keep y   0x0000000000000715 in print at gdb_example.c:5
4       breakpoint     keep y   0x0000000000000709 in print at gdb_example.c:4
5       breakpoint     keep y   0x0000000000000709 in print at gdb_example.c:4

显示栈帧
backtrace命令可以在遇到断点而暂停执行时显示栈帧。该命令简写为bt。此外, backtrace的别名还有where和info stack(简写为info s)。

(gdb) b 4
Breakpoint 1 at 0x714: file gdb_example.c, line 4.
(gdb) r
Starting program: /home/zhongyi/code/example/gdb_example 
In main():
   x is 10 and is stored at 0x7fffffffe2fc.
   ptr points to 0x7fffffffe2fc which holds 10.
In print():
   xx is 10 and is stored at 0x7fffffffe2cc.
   ptr points to 0x7fffffffe2fc which holds 10.
In sum():
In minus():

Breakpoint 1, minus (a=3, b=2) at gdb_example.c:4
4          int c = a-b;
# 显示栈帧
(gdb) bt
#0  minus (a=3, b=2) at gdb_example.c:4
#1  0x00005555555547c0 in print (xx=10, xxptr=0x7fffffffe2fc) at gdb_example.c:17
#2  0x0000555555554841 in main () at gdb_example.c:28
#只显示前2个栈帧
(gdb) bt 2
#0  minus (a=3, b=2) at gdb_example.c:4
#1  0x00005555555547c0 in print (xx=10, xxptr=0x7fffffffe2fc) at gdb_example.c:17
(More stack frames follow...)
# 从外向内显示2个栈帧,及其局部变量
(gdb) bt full -2
#1  0x00005555555547c0 in print (xx=10, xxptr=0x7fffffffe2fc) at gdb_example.c:17
        c = 5
        d = 21845
#2  0x0000555555554841 in main () at gdb_example.c:28
        x = 10
        ptr = 0x7fffffffe2fc
(gdb) 

显示变量

(gdb) p x
$1 = 10
(gdb) p ptr
$2 = (int *) 0x7fffffffe2fc
(gdb) 

显示寄存器

(gdb) info reg
rax            0xc      12
rbx            0x0      0
rcx            0x7ffff7af2104   140737348837636
rdx            0x7ffff7dcf8c0   140737351841984
rsi            0x555555756260   93824994337376
rdi            0x1      1
rbp            0x7fffffffe310   0x7fffffffe310
rsp            0x7fffffffe2f0   0x7fffffffe2f0
r8             0x7ffff7fe14c0   140737354011840
r9             0x0      0
r10            0x0      0
r11            0x246    582
r12            0x5555555545f0   93824992232944
r13            0x7fffffffe3f0   140737488348144
r14            0x0      0
r15            0x0      0
rip            0x555555554841   0x555555554841 <main+123>
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

寄存器前加$,可以显示寄存器的内容。

(gdb) p $rdi
$7 = 1
(gdb) p $rax
$8 = 12
(gdb) 
显示寄存器可以用以下格式
p/格式 变量
格式说明 
x 显示为16进制数 
d 显示为十进制数 
u 显示为无符号十进制数 
o 显示为八进制数 
t 显示为二进制数 
a 地址 
c 显示为ascii 
f 浮点小数 
s 显示为字符串 
i 显示为机器语言(仅在显示内存的x命令中可用)

显示内存

(gdb) x $r12
   0x5555555545f0 <_start>:     xor    %ebp,%ebp
(gdb) x $r8
   0x7ffff7fe14c0:      rclb   $0xf7,(%rsi,%rdi,8)
(gdb) 
x/i 可以显示汇编指令。
一般用x命令时,格式为x/NFU ADDR。此处ADDR为希望显示的地址,N为重复次数。F为前面讲过的格式,
u代表的单位如下。
b 字节 h 半字(2字节) w 字(4字节) g 双字(8字节)

下面显示从rsp开始的10条指令

(gdb) x/10i $rsp
   0x7fffffffe2f0:      (bad)  
   0x7fffffffe2f1:      rex.W push %rbp
   0x7fffffffe2f3:      push   %rbp
   0x7fffffffe2f4:      push   %rbp
   0x7fffffffe2f5:      push   %rbp
   0x7fffffffe2f6:      add    %al,(%rax)
   0x7fffffffe2f8:      lock rex.RB push %r13
   0x7fffffffe2fb:      push   %rbp
   0x7fffffffe2fc:      or     (%rax),%al
   0x7fffffffe2fe:      add    %al,(%rax)

显示反汇编
disassemble //反汇编当前整个函数
disassemble 程序计数器 //反汇编程序计数器所在函数的整个函数
disassemble 开始地址 结束地址 //反汇编从开始地址到结束地址的部分

(gdb) disassemble 
Dump of assembler code for function sum:
   0x0000555555554722 <+0>:     push   %rbp
   0x0000555555554723 <+1>:     mov    %rsp,%rbp
   0x0000555555554726 <+4>:     sub    $0x20,%rsp
   0x000055555555472a <+8>:     mov    %edi,-0x14(%rbp)
   0x000055555555472d <+11>:    mov    %esi,-0x18(%rbp)
   0x0000555555554730 <+14>:    lea    0x1bd(%rip),%rdi        # 0x5555555548f4
   0x0000555555554737 <+21>:    callq  0x5555555545b0 <puts@plt>
=> 0x000055555555473c <+26>:    mov    -0x14(%rbp),%edx
   0x000055555555473f <+29>:    mov    -0x18(%rbp),%eax
   0x0000555555554742 <+32>:    add    %edx,%eax
   0x0000555555554744 <+34>:    mov    %eax,-0x4(%rbp)
   0x0000555555554747 <+37>:    mov    -0x4(%rbp),%eax
   0x000055555555474a <+40>:    leaveq 
   0x000055555555474b <+41>:    retq   
End of assembler dump.

执行

执行源代码中的一行:next
进入函数内部执行:step
逐条执行汇编指令:nexti,stepi
继续运行: continue 次数
==> 指定次数可以忽略断点,例如,continue 55次遇到断点不会停止,第6次遇到断点才会停止。

监视点

watch <表达式>
<表达式>发生变化时暂停运行,<表达式>意思是常量或变量

awatch <表达式>
<表达式>被访问,改变时暂停运行

rwatch <表达式>
<表达式>被访问时暂停运行

(gdb) watch c
Hardware watchpoint 2: c
(gdb) c
Continuing.

Hardware watchpoint 2: c

Old value = 21845
New value = 5
sum (a=2, b=3) at gdb_example.c:10
10          return c;
(gdb) 

删除断点和监视点

delete <编号>
<编号>指的是断点或监视点

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555473c in sum at gdb_example.c:9
        breakpoint already hit 1 time
2       hw watchpoint  keep y                      c
        breakpoint already hit 1 time
(gdb) delete  2
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555473c in sum at gdb_example.c:9
        breakpoint already hit 1 time
(gdb) 

改变变量的值

set variable <变量>=<表达式>

(gdb) p c
$1 = 5
(gdb) set variable c=0
(gdb) p c
$2 = 0
(gdb) 

生成内核转储文件

(gdb) generate-core-file 
warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000.
Saved corefile core.2380

有了内核转储文件,即使退出了GDB也能查看生成转储文件时的运行历史。

gcore 'pidof gdb_example'

该命令无需停止正在运行的程序,可以直接从命令行直接生成转储文件。当需要在其他机器上单独分析问题原因时,或者是分析客户现场问题时十分有用。

条件断点

break 断点 if 条件 // 如果条件为真,则暂停运行
condition 断点编号 // 删除指定断点编号的触发条件
condition 断点编号 条件 // 给断点添加触发条件

反复执行
ignore 断点编号 次数

在编号指定的断点,监视点忽略指定的次数
continue与ignore一样,也可以指定次数,达到指定次数前,执行到断点时不暂停。

continue次数
step 次数
stepi 次数
next 次数
nexti 次数

finish //执行完当前函数后暂停
until  //执行完当前函数等代码块后暂停,常用于跳出循环。
until 地址

删除断点或禁用断点

clear 
clear 函数名
clear 行号
clear 文件名:行号
clear 文件名:函数名
delete [breakpoints] 断点编号

disable 临时禁用断点

disable [breakpoints] 
disable [breakpoints] 断点编号
disable display 显示编号
disable mem 内存区域

enable
enable [breakpoints] 断点编号
enable [breakpoints] once 断点编号
enable [breakpoints] delete 断点编号
enable disable display 显示编号
enable  mem 内存区域
once 使指定的断点只启用一次。delete表示在运行暂停后删除断点。

断点命令

(gdb) b 17
Breakpoint 3 at 0x5555555547b1: file gdb_example.c, line 17.
(gdb) command 3
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>p c
>end
(gdb) r
Starting program: /home/zhongyi/code/example/gdb_example -e 'p 1'
In main():
   x is 10 and is stored at 0x7fffffffe2ec.
   ptr points to 0x7fffffffe2ec which holds 10.
In print():
   xx is 10 and is stored at 0x7fffffffe2bc.
   ptr points to 0x7fffffffe2ec which holds 10.
In sum():

Breakpoint 3, print (xx=10, xxptr=0x7fffffffe2ec) at gdb_example.c:17
17        int d = minus(3,2);
$1 = 5

上例表示在17行暂停后打印c的值。
与前面的条件断点组合使用,可以在断点暂停时执行复杂的动作。

常用命令及其缩略形式

backtrace bt/where 显示backtrace break 设备断点 
continue c/cont 继续运行 
delete d 删除断点 
finish 运行到函数结束 
info breakpoints 显示断点信息 
next n 执行下一行 
print p 显示表达式 
run r 运行程序 
step s 一次执行一行,包括函数内部 
x 显示内存内容 
until u 执行到指定行 
directory dir 插入目录 
disable dis 禁用断点 
down do 在当前栈帧中选择要显示的栈帧 
edit e 编辑文件或函数 
frame f 选择要显示的栈帧 
forward-search fo 向前搜索 
generate-core-file gcore 生成内核转储 
help h 显示帮助文档 
info i 显示信息 list l 显示函数行 
nexti ni 执行下一行(以汇编代码为单位) 
print-object po 显示目标信息 
sharedlibrary share 加载共享库的符号 
stepi si 执行下一行

值的历史

通过print命令显示过的值会记录在内部的值历史中,这些值可以在其他表达式中使用。
最后的值可以使用$ 访问
通过show values 可以显示历史中的最后10个值

(gdb) b 16
Breakpoint 1 at 0x79f: file gdb_example.c, line 16.
(gdb) b 17
Breakpoint 2 at 0x7b1: file gdb_example.c, line 17.
(gdb) b 29
Breakpoint 3 at 0x841: file gdb_example.c, line 29.
(gdb) r
Starting program: /home/zhongyi/code/example/gdb_example 
In main():
   x is 10 and is stored at 0x7fffffffe2fc.
   ptr points to 0x7fffffffe2fc which holds 10.
In print():
   xx is 10 and is stored at 0x7fffffffe2cc.
   ptr points to 0x7fffffffe2fc which holds 10.

Breakpoint 1, print (xx=10, xxptr=0x7fffffffe2fc) at gdb_example.c:16
16        int c = sum(2,3);
(gdb) p c
$1 = 1431651824
(gdb) c
Continuing.
In sum():

Breakpoint 2, print (xx=10, xxptr=0x7fffffffe2fc) at gdb_example.c:17
17        int d = minus(3,2);
(gdb) p c
$2 = 5
(gdb) c
Continuing.
In minus():

Breakpoint 3, main () at gdb_example.c:29
29        return 0;

值的历史的访问变量和说明

变量说明

$ 值历史中的最后一个值 
$n 值历史的第n个值 
$$ 值历史的倒数第二个值 
$$n 值历史的倒数第n个值 
$_ x命令显示过的最后的地址 
$__ x命令显示过的最后的地址的值 
$_exitcode 调试中的程序的返回代码 
$bpnum 最后设置的断点的编号

可以随意定义变量。变量以$开头,由英文和数字组成。

(gdb) set $i=0
(gdb) p $i
$5 = 0
(gdb) 

命令历史
把命令保存在文件中,保存命令历史后,可以在其他调试会话中使用。
默认命令历史文件位于 ./.gdb_history

set history expansion
show history expansion

可将命令历史保存到文件中,可以通过环境变量GDBHISTFILE改变默认文件。
set history save
show history save

启用命令历史保存到文件和恢复的功能。
set history size 数字
show history size
设置保存到命令历史中的命令数量,默认为256

初始化文件(.gdbinit)
Linux下gdb初始化文件为.gdbinit。如果存在.gdbinit文件,GDB在启动之前将其作为命令文件运行。
顺序如下:

$HOME/.gdbinit
运行命令行选项
./.gdbinit
加载通过-x选项给出的命令文件

命令定义
用 define 自定义命令,用 document 给自定义的命令加说明,利用 help 命令名查看定义的命令。

define格式:

define 命令名
 命令
 …………
 end
document格式:

document 命令名
 说明
 end
help格式:

help 命令名

示例定义名为li的命令

(gdb) define li
Type commands for definition of "li".
End with a line saying just "end".
>x/10i $rbp
>end
(gdb) document li
Type documentation for "li".
End with a line saying just "end".
>list machine instruction
>end
(gdb) li
   0x7fffffffe310:      (bad)  
   0x7fffffffe311:      rex.W push %rbp
   0x7fffffffe313:      push   %rbp
   0x7fffffffe314:      push   %rbp
   0x7fffffffe315:      push   %rbp
   0x7fffffffe316:      add    %al,(%rax)
   0x7fffffffe318:      xchg   %edi,(%rax,%riz,4)
   0x7fffffffe31b:      idiv   %edi
   0x7fffffffe31d:      jg     0x7fffffffe31f
   0x7fffffffe31f:      add    %al,(%rcx)
(gdb) help li
list machine instruction

参考gdb手册的相关章节:Extending GDB (https://sourceware.org/gdb/onlinedocs/gdb/Extending-GDB.html)。

推荐一个github上的.gdbinit文件:https://github.com/gdbinit/Gdbinit,弄懂gdb脚本文件。

进阶
https://blog.csdn.net/lyshark_lyshark/article/details/125846778
https://www.toutiao.com/article/6829905020427698696
https://www.toutiao.com/article/7040340096540918303

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值