【问题标题】:ptrace doesnt show the same as objdumpptrace 与 objdump 显示的不同
【发布时间】:2020-04-30 20:12:26
【问题描述】:

我正在编写一个使用 ptrace 显示指令的 C 程序。这是代码:

#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <string.h>

void run_target()
{
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    execl("./test", "test", NULL);
}

void debugger(pid_t pid)
{
    int status;
    wait(&status);

    while(WIFSTOPPED(status))
    {
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, pid, 0, &regs);
        long instruction = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);
        ptrace(PTRACE_SINGLESTEP, pid, 0, 0);

        //EDITED SO IT PRINTS ONLY LINES I WANT
        if(((regs.rip >> (8*5)) & 0xFF) != 0x7f)    //i noticed all the junk lines that shouldnt be there, their regs.rip began with 7f
            printf("%llx %16lx\n", regs.rip, instruction);

        wait(&status);
    }
}

int main()
{
    pid_t pid;
    pid = fork();

    if(pid == 0)
    {
        run_target();
    }
    else
    {
        debugger(pid);
    }

    return 0;
}

但输出看起来像这样

...
7f3bf1487308 8348da7426fa8348
7f3bf148730c d47408fa8348da74
7f3bf148730e 8d48d47408fa8348
7f3bf1487312 16ad50d8d48d474
7f3bf14872e8 18c0834810508b48
7f3bf14872ec 48f2014c18c08348
7f3bf14872f0 8948c33948f2014c
7f3bf14872f3 860f118948c33948
7f3bf14872f6 fff670860f118948
7f3bf14872f9 508bfffff670860f

而 objdump -d 看起来像这样:

000000000000064a <main>:
64a:    55                      push   %rbp
64b:    48 89 e5                mov    %rsp,%rbp
64e:    48 8d 3d af 00 00 00    lea    0xaf(%rip),%rdi        # 704 <_IO_stdin_used+0x4>
655:    b8 00 00 00 00          mov    $0x0,%eax
65a:    e8 c1 fe ff ff          callq  520 <printf@plt>
65f:    48 8d 3d a5 00 00 00    lea    0xa5(%rip),%rdi        # 70b <_IO_stdin_used+0xb>
666:    b8 00 00 00 00          mov    $0x0,%eax
66b:    e8 b0 fe ff ff          callq  520 <printf@plt>
670:    b8 00 00 00 00          mov    $0x0,%eax
675:    5d                      pop    %rbp
676:    c3                      retq   
677:    66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
67e:    00 00 

例如,运行 ./program | grep "64e" 会显示这一点

7f46f3bb64e2 89483e8b48f90148
7f46f3bb64e5 8b483989483e8b48
7f46f3bb64e8 48087e8b48398948
7f46f3bb64eb 48c70148087e8b48
7f46f3bb64ef 4808798948c70148
7f46f3bb7318 f64ee8ef894c9174
7f46f3bb731a f64ee8ef894c
7f46f3bb731d 4887eb0000f64ee8
7f46f3800b70 4864e889481f8948
7f46f3800b73 2504334864e88948
55dfb208464e b8000000af3d8d48
7f46f38564e2 89440af883481476
7f46f38f2a11 4334864e8458b48
7f46f380505a e264e8ff894c0874

这个实际上是正确的:

55dfb208464e b8000000af3d8d48

那么 objdump 中是否缺少某些内容,或者我的代码中是否缺少某些内容,它显示的 rip 比应有的更多?

编辑:添加整个代码

编辑:编辑了 printf,现在它打印了它应该打印的行,但它仍然在开头添加了随机十六进制 应该是什么

64a: 55

它是什么

56160a60a64a 9f3d8d48e5894855

我明白为什么指令不一样,这不是我的问题,我的问题是为什么 regs.rip 不是 64a,而是 64a。每次我重新运行程序时,开始的随机十六进制都是不同的。

【问题讨论】:

  • 次要:建议printf("%llx %16lx\n", regs.rip, instruction); 以获得更好的指令对齐。
  • @bruno 是的,我在同一个程序上同时执行 objdump 和 ptrace(其中只有 2 个 printf)
  • @bruno PTRACE_TRACEME 和 PTRACE_SINGLESTEP 都返回 0 并且 PTRACE_PEEKTEXT 返回指令
  • @bruno 如果我在打印时没有放 %llx,那么编译器会警告我应该这样做
  • @qlabfgerkaSmurf 您看到的输出是有道理的,我可能有答案,但为了确保我需要更多信息。展示你在孩子身上所做的事情(即 C 代码)。添加minimal reproducible example 是最好的主意,否则回答只是令人不快的猜测。另外,请说明您使用的是哪个发行版 (lsb_release -a) 和您的 libc 版本 (apt-cache policy libc6)。

标签: c objdump ptrace


【解决方案1】:

您的程序有效,您只需在结果中以正确的顺序查看正确的位置。

拥有该测试定义:

#include <stdio.h>
int main()
{
  puts("hello");
  puts("world");
  return 0;
}

objdump -d test 除其他外产生:

0000000000400526 <main>:
  400526:   55                      push   %rbp
  400527:   48 89 e5                mov    %rsp,%rbp
  40052a:   bf d4 05 40 00          mov    $0x4005d4,%edi
  40052f:   e8 cc fe ff ff          callq  400400 <puts@plt>
  400534:   bf da 05 40 00          mov    $0x4005da,%edi
  400539:   e8 c2 fe ff ff          callq  400400 <puts@plt>
  40053e:   b8 00 00 00 00          mov    $0x0,%eax
  400543:   5d                      pop    %rbp
  400544:   c3                      retq   
  400545:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40054c:   00 00 00 
  40054f:   90                      nop

如果我在结果中运行你的程序,我发现代码具有相同的 objdump 地址和相同的指令代码,所以 init 部分等然后 main(来自第 81208 行!):

400526 4005d4bfe5894855
400527   4005d4bfe58948
40052a fecce8004005d4bf
40052f  5dabffffffecce8
400400   6800200c1225ff
400406 ffe0e90000000068
40040b  a25ffffffffe0e9
4003f0 25ff00200c1235ff
4003f6 1f0f00200c1425ff
...

我在 Intel i7 上运行,指令字节必须以与 objdump 所示相反的顺序读取,例如第一条指令是 55,然后 48 89 e5 然后 bf d4 05 40 00 然后 e8 cc fe ff ff 等等。

当然,在您的情况下,就像您在调试器中执行“步骤”而不是“下一步”一样,因此您在 callqputs 中输入>puts,而 objdump 反汇编。在地址 40052f 之后有 60084 行到达地址 400534,所以需要 60084 条指令来做 puts("hello");


当然可以只从 main 的开头打印,例如使用 objdump 的惰性方式:

void debugger(pid_t pid)
{
  FILE * fp = popen("objdump -d ./test | grep \"<main>:\"", "r");

  if (fp == NULL) {
    puts("cannot get main address");
  }
  else {
    char line[256];

    if (fgets(line, sizeof(line), fp) == NULL) {
      puts("no address !");
      pclose(fp);
    }
    else {
      unsigned long long main_addr;

      pclose(fp);
      errno = 0;
      main_addr = strtoull(line, NULL, 16);

      if (errno != 0)
        puts("invalid address");
      else {
        int found = 0;
        int status;

        while(wait(&status), WIFSTOPPED(status))
        {
          struct user_regs_struct regs;

          ptrace(PTRACE_GETREGS, pid, 0, &regs);

          if (found |= (regs.rip == main_addr)) {
            long instruction = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);

            printf("%llx %16lx\n", regs.rip, instruction);
          }

          ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
        }
      }
    }
  }
}

[编辑您的评论]

ptrace 不知道你是要读取数据还是指令,如果你想将每条指令分开为 objdump 你必须知道每个指令的代码和相应的长度,否则您可以使用从一个到下一个的地址差异,即使在 call/jmp/conditional jmp/return 的情况下不起作用:

void debugger(pid_t pid)
{
  FILE * fp = popen("objdump -d ./test | grep \"<main>:\"", "r");

  if (fp == NULL) {
    puts("cannot get main address");
  }
  else {
    char line[256];

    if (fgets(line, sizeof(line), fp) == NULL) {
      puts("no address !");
      pclose(fp);
    }
    else {
      unsigned long long main_addr;

      pclose(fp);
      errno = 0;
      main_addr = strtoull(line, NULL, 16);

      if (errno != 0)
        puts("invalid address");
      else {
        int found = 0;
        int status;
        unsigned long prev_instr;
        long long prev_addr = -1;

        while(wait(&status), WIFSTOPPED(status))
        {
          struct user_regs_struct regs;

          ptrace(PTRACE_GETREGS, pid, 0, &regs);

          if (found |= (regs.rip == main_addr)) {
            unsigned long instruction =
              (unsigned long) ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);

            if (prev_addr != -1) {
              /* longest instruction has 15 bytes on x86 */
              int len = ((regs.rip > prev_addr) && ((regs.rip - prev_addr) <= 15))
                ? regs.rip - prev_addr : 100;

              printf("%llx ", prev_addr);
              while (prev_instr && len--) {
                printf("%02x ", (unsigned) (prev_instr & 0xff));
                prev_instr /= 256;
              }
              if (len > 15)
                puts(" (?)");
              else
                putchar('\n');
            }
            prev_instr = instruction;
            prev_addr = regs.rip;
          }

          ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
        }
      }
    }
  }
}

现在的输出是:

400526 55 
400527 48 89 e5 
40052a bf d4 05 40 00 
40052f e8 cc fe ff ff bf da 05  (?)
400400 ff 25 12 0c 20 00 
400406 68 00 00 00 00 
40040b e9 e0 ff ff ff ff 25 0a  (?)
4003f0 ff 35 12 0c 20 00 
4003f6 ff 25 14 0c 20 00 0f 1f  (?)
7fe20cc1fe10 53 
7fe20cc1fe11 48 89 e3 
7fe20cc1fe14 48 83 e4 c0 
7fe20cc1fe18 48 2b 25 31 df 20 00 
7fe20cc1fe1f 48 89 04 24 
7fe20cc1fe23 48 89 4c 24 08 
...

要做到更多,你必须知道每条指令 call/jmp/conditional jmp/return 的代码和长度。注意代码操作可以是 1 或 2 个字节。

【讨论】:

  • 我写的是同样的答案,但你先明白了,做得好;)
  • @bruno 也许我可能错过了你所说的重点,我对答案并不完全满意,你在我的程序中添加了很多代码,我并不完全想要在其中,例如打开一个文件,因为只有 execl 在孩子中才能做到这一点。而且我仍然不太明白为什么当我在我的机器上运行程序时指令 64a 显示为 64a
  • 我最后的更改是回答您的评论,之后您将其删除。 "opening a file" : 不是文件,fopenpopen 不是一回事。 "指令 64a" : 64e 不是指令。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-05
  • 2011-11-08
  • 1970-01-01
相关资源
最近更新 更多