调试器工作原理(1):基础篇

原著原作 : 

本文是圆形的探查术调试器任务规律的文字的第一篇。我半信半疑这篇文字包含了编号文字和动机。,但据我看来从初步开端。。

顾虑本文

我计划在这篇文字中引见顾虑Linux下的调试器成功的首要组成使成比例——表示零碎调动。本文正中鹄的编码是在独身32位Ubuntu零碎上发展的。。请注重,嗨的编码与平台紧密中间定位。,还移居到别的平台上不太故障。。

动机

知情我们家要做什么。,试设想象一下调试器是若何任务的。调试器可以启动少数行动方向,此后调试它。,或许将本人联系在一起到存在的行动方向。。它可以缓缓地运转编码。,设置断点并运转顺序。,反省变量的值并后面的调动堆栈。。许多的调试器曾经扣留了相当多的较高的特点,比如,在海报中抬出去陈述并调动应变量。,您甚至可以直截了当地修正转换的编码并留心修正。。

虽有时髦人士的调试器都是复杂的巨型顺序,但参加意外发现的是建筑物调试器的根底确是很的复杂。调试器只用到了一些由推拿零碎又编辑者/联系在一起器出价的根底效劳,剩的纯粹设计成绩。。(参观维基百科对此条挥向解说。),作者具有挖苦意味。

Linux下的调试

Linux下调试器扣留独身瑞士军刀般的器,这是pTrace零碎调动。。这是独身功用很多、功用构成复杂的器。,容许独身行动方向把持另独身行动方向的运转。,并且可以被监控和漏到为了转换中。。表示自己需求一本卫星工夫的长短的书才干对其终止充分的解说,这执意为什么我只计划经过包围把主旨放在它的现实目的上。让我们家持续深化讨论。。

遍历转换的编码

我要写独身在后面的形式下运转的转换的包围。,嗨我们家要单步遍历为了行动方向的编码——由CPU所抬出去的机器代码(缀编命令)。我会给你独身包围编码在嗨。,解说每个使成比例,在本文的末了,您可以下载充分的C顺序发送。,你可以本人编辑和抬出去。。铁路记号所设计,我们家必然要组成独身顺序。,它构造独身子转换来抬出去用户详述的命令。,父行动方向后面的此子行动方向。。率先,首要功用是这样地样的。:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

intmain(intargc,char**argv)

{

    pid_t child_pid;

    if(argc<2){

        fprintf(stderr,“Expected a program name as argument\n”);

        return1;

    }

    child_pid=fork();

    if(child_pid==0)

        run_target(argv[1]);

    elseif(child_pid>0)

        run_debugger(child_pid);

    else{

        perror(“fork”);

        return1;

    }

    return0;

}

编码相当复杂。,我们家经过叉子构造独身新的子转换。。后续if判决块处置子行动方向(嗨称为TAR)。,而else if判决块处置父行动方向(嗨称为“调试器”)。以下是目的转换。:

1

2

3

4

5

6

7

8

9

10

11

12

13

voidrun_target(constchar*programname)

{

    procmsg(“target
接入。 will run ”%s”\n”
,programname);

    /*
Allow tracing of this process */

    if(表示(PTRACE_TRACEME,0,0,0)<0){

        perror(“表示”);

        return;

    }

    /*
Replace this 转换的 image with the given program */

    execl(programname,programname,0);

}

这使成比例最有意思的本地居民在表示调动。表示的蓝本是(在sys/):

1

long表示(enum__表示_request request,  pid_t pid,void*addr,  void*data);

第独身决定因素是请求说服。,它可以是独身预先裁明确的常数值,从p循迹开端。。第二份食物个决定因素详述行动方向ID。,第三和四分之一决定因素是地址和指路档案的拨弄。,用于推拿内存。。下面编码段正中鹄的表示调动应用了PTRACE_TRACEME请求说服,这标明为了子转换需求推拿零碎内核。为了请求在手册中解说得很整整。:

提示该行动方向由其父行动方向后面的。。发送到为了行动方向的什么都可以记号(此外SIGKEY)首府领到行动方向出错。,它的父行动方向将经过观望形势后再作决定圆形的。独白,该行动方向然后各种的对exec()的调动都将使推拿零碎发作独身SIGTRAP记号发发出信息它,这给父行动方向出价了把持子行动方向B的机遇。。万一您不愿望被父行动方向后面的,这样地你不宜应用为了请求说服。。(PID)、addr、档案被疏忽)

我重读了为了包围,我们家感兴趣。。注重,run_target在表示调动然后接着做的是经过execl来调动我们家详述的顺序。这将被解说为我们家鼓出使成比例的一使成比例。,推拿零碎的内核在子行动方向先前终止行动方向。,并向父行动方向发送独身记号。。

于是,是时辰看一眼父行动方向需求做什么了。:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

voidrun_debugger(pid_t
child_pid)

{

    intwait_status;

    unsignedicounter=0;

    procmsg(“debugger
started\n”
);

    /*
Wait for child to stop on its first instruction */

    wait(&wait_status);

    while(WIFSTOPPED(wait_status)){

        icounter++;

        /*
Make the child execute another instruction */

        if(表示(PTRACE_SINGLESTEP,child_pid,0,0)<0){

            perror(“表示”);

            return;

        }

        /*
Wait for child to stop on its next instruction */

        wait(&wait_status);

    }

    procmsg(“the
child executed %u instructions\n”
,icounter);

}

经过下面的编码我们家可以评论一下,子行动方向开端抬出去EXEC调动后,它将终止并接纳SIGRAT记号。。父行动方向在观望形势后再作决定经过最初WEW发作此事变。。一旦子行动方向终止,万一子行动方向终止运转,由于,WiFrue归还true),父行动方向反省事变。。

父行动方向接下来将做是什么为了AR最风趣的使成比例。。父行动方向经过PTRACE_SINGLESTEP又子行动方向的id号来调动表示。这样地做是告知推拿零碎——请重行促进者行动方向,还当子行动方向抬出去下独身命令时,它终止。。此后父行动方向观望形势后再作决定该子再次终止。,总计的流传持续抬出去。。当您从观望形势后再作决定中说服记号时,它找错误顾虑终止孩子们PRO的。,流传完毕。在为了后面的顺序的规则运转转换中,它将说服子行动方向规则撤离的记号(WIFFEXIT)。

icounter会统计数字子行动方向抬出去的命令总额。于是我们家为了复杂的包围现实上静止的做了点有帮助的的事实——经过在命令行上详述独身顺序名,我们家的示例将抬出去详述的顺序。,此后,顺序从B抬出去的CPU命令总额。。让我们家来看一眼现实推拿。。

现实测量法

我组成了以下复杂的顺序。,此后在我们家的后面的顺序下抬出去。:

#include

intmain()

{

    printf(Hello,world!\n);

    return0;

}

令我胡乱干的工作的是,,我们家的后面的顺序运转了很长的工夫此后方言显示一共有权超越100000条命令说服了抬出去。这纯粹独身复杂的蜡纸油印件请求说服。,为什么会这样地样?答案很风趣。。默许条款下,Linux正中鹄的gcc编辑者会动态联系在一起到C运转时库。这谓语什么都可以顺序运转的第一件事执意附加费动态。。这需求大批的编码成功——铭记不忘,我们家的复杂后面的顺序将对抬出去的每个命令终止计数。,这不仅仅是首要的功用。,但总计的转换。。

于是,当我采取-static指出动态联系在一起为了测量法顺序时(注重到可抬出去发送于是增强了500KB的大多数,由于它动态衔接C运转库。,我们家的后面的顺序方言显示只要7000条摆布的命令被抬出去了。这依然是很多。,还万一您对某人找岔子LIBC的设定初值依然在EXE先前,洗涤推拿将在主控后终止。,嗯,这是合乎情理的。。并且,Primtf亦独身复应变量。。

我们家对此仍不愉快。,我愿望能注意到相当多的东西。,比如,我可以从全面上注意到每个命令需求抬出去什么。。这可以经过缀编编码成功。。因而我把为了夏威夷群岛的书面缩写。,关于全球大局的顺序缀编(GCC)) s)是下沉。:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

section    .text

    ;The
_start
symbol
must
be
declared
forthe
linker(ld)

    global_start

_start:

    ;Prepare
arguments
forthe
sys_write
system
call:

    ;  
eax:system
call
number(sys_write)

    ;  
ebx:file
descriptor(stdout)

    ;  
ecx:pointer
tostring

    ;  
edx:stringlength

    mov    edx,len

    mov    ecx,msg

    mov    ebx,1

    mov    eax,4

    ;Execute
the
sys_write
system
call

    int    0x80

    ;Execute
sys_exit

    mov    eax,1

    int    0x80

section  
.data

msg
db    夏威夷群岛的书面缩写,
world!”
,0xa

len
equ    $msg

这就十足了。如今后面的顺序将方言曾经抬出去了7条命令。,我可以易于地从缀编编码中坚信礼这点。。

深远的命令流

缀编码顺序足以让我为完全地引见表示的另独身可怕的的功用——明确的反省被后面的行动方向的国务的。嗨是Run-Unjiggor应变量的另独身版本。:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

voidrun_debugger(pid_t child_pid)

{

    intwait_status;

    unsignedicounter=0;

    procmsg(“debugger started\n”);

    /* Wait for child to stop on its first instruction */

    wait(&wait_status);

    while(WIFSTOPPED(wait_status)){

        icounter++;

        structuser_regs_struct regs;

        表示(PTRACE_GETREGS,child_pid,0,®s);

        unsignedinstr=表示(PTRACE_PEEKTEXT,child_pid,regs.eip,0);

        procmsg(“icounter = 美国抄本 = 0x%08x.  instr = 0x%08x\n”,

                    icounter,regs.eip,instr);

        /* Make the child execute another instruction */

        if(表示(PTRACE_SINGLESTEP,child_pid,0,0)<0){

            perror(“表示”);

            return;

        }

        /* Wait for child to stop on its next instruction */

        wait(&wait_status);

    }

    procmsg(“the child executed %u instructions\n”,icounter);

}

与前一版本相形,只的分别是while流传的开端。。嗨有两个新的表示调动。第独身读取行动方向的表示值到独身排列体中。零碎中明确了用户排列。这是独身风趣的本地居民,万一你翻开为了头发送。,在发送顶部左近有这样地样的正文。:

1

/* 本发送的只挥向是GDB。,并且只依从的GDB。。向这份发送,不要看过度。。除非GDB,不同的不要应用什么都可以别的目的。,除非你晓得本人在做什么。。*/

如今,我不晓得你在想什么。,但我觉得我们家是在特赞的轨道上。。无论若何,回到我们家的包围。。一旦我们家开腰槽各种的表示值,,我们家就可以经过PTRACE_PEEKTEXT指出又将(x86架构上的冲洗命令拨弄)做决定因素传入表示来调动。我们家说服的是引导。。让我们家在缀编编码上运转后面的顺序的新释放。。

$simple_tracer traced_helloworld

[5700]debugger started

[5701]target started.will run”traced_helloworld”

[5700]icounter=1.  EIP=0x08048080.  instr=0x00000eba

[5700]icounter=2.  EIP=0x08048085.  instr=0x0490a0b9

[5700]icounter=3.  EIP=0x0804808a.  instr=0x000001bb

[5700]icounter=4.  EIP=0x0804808f.  instr=0x000004b8

[5700]icounter=5.  EIP=0x08048094.  instr=0x01b880cd

Hello,world!

[5700]icounter=6.  EIP=0x08048096.  instr=0x000001b8

[5700]icounter=7.  EIP=0x0804809b.  instr=0x000080cd

[5700]the childexecuted7instructions

OK,因而如今此外IcOnter。,我们家也可以在每个踏中注意到命令和命令。。我们家若何坚信礼这种特赞性?我们家可以在可抬出去FIL上抬出去ObjDIP D抬出去:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

$objdumpdtraced_helloworld

traced_helloworld:    
file
formatelf32i386

Disassembly
of
section.text:

08048080<.text>:

8048080:    
ba0e000000          mov    $0xe,%edx

8048085:    
b9
a0900408          mov    $0x80490a0,%ecx

804808a:    
bb01000000          mov    $0x1,%ebx

804808f:    
b804000000          mov    $0x4,%eax

8048094:    
cd80                  
int    $0x80

8048096:    
b801000000          mov    $0x1,%eax

804809b:    
cd80                  
int    $0x80

应用为了输入来构成我们家的后面的顺序输入。,留心同独身本地居民宜易于。。

与运转转换关于

你曾经晓得了调试器也可以关系到曾经做运转国务的的行动方向上。注意到嗨,你不宜觉得意外发现。,这亦经过表示来成功的。这需求pTraceEnAsple请求说服。。我不见得给你独身范本编码在嗨。,由于我们家曾经看过编码了。,这宜易于成功。。本教导的挥向,嗨的方式更便利(由于我们家可以无准备地终止孩子)。。

编码

本文装备的为了复杂的后面的顺序的充分编码(更较高的少量的,你可以蜡纸油印件出详细的命令)你可以在嗨找到它们。。经过Wall的顺序
–pedantic STD=C99编辑调动球员是在GCC版本上编辑的。。

断定和下一步要做的。

固然,本文并没有包含过度的材料——我们家离独身真正合用的的调试器还差的有多远。还,我愿望这篇文字曾经揭开了调试转换正中鹄的神秘的。。表示是独身扣留许多的功用的零碎调动,眼前,我们家只上演了这些功用正中鹄的一些。。

能逐级抬出去编码是有帮助的的。,但胜利限定。。用夏威夷群岛的书面缩写, 以关于全球大局的为例,获得首要功用,您需求经过数千条命令来设定初值C运转时。。这找错误很便利。。我们家打算的抱负receiver 收音机是在MAI引入设置断点。,从断点抬出去单步抬出去。鄙人一篇文字中,我将引见若何成功断点机制。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

`