调试器工作原理(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运转时。。这变动从而产生断层很出恭。。敝为特定用途而打算的抱负receive 接收是在MAI进食设置断点。,从断点器械单步器械。鄙人一篇文字中,我将引见多少产生断点机制。。

发表评论

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

`