Introduction

回顾 Hello World

#include <stdio.h>

int main(){
   printf("Hello, World\n");
   return 0;
}

看似简单的 Hello World 其实一点也不简单。当使用 objdump -d a.out 查看 gcc 编译出来的结果时,会发现 a.out 中其实包含了很多东西。在程序执行时,是由谁调用的 main(),而 main()return 时又将返回哪里?当没有 libc 时,我们应该如何让一个程序正常结束?

系统调用和最小 Hello World

用户程序只能通过系统调用 (以操作系统所允许的方式) 来向操作系统请求服务。为了在屏幕上打印 Hello World,我们仅需要请求两个服务:write()exit()

// hello.S
.section .data
msg:    .asciz "Hello World\n" ; 

.section .text
.globl _start

_start:
  movq $1,   %rax   // write system call
  movq $1,   %rdi   // file descriptor
  movq $msg, %rsi   // pointer to message
  movq $12,  %rdx   // message length
  syscall           

  movq $60,  %rax   // exit system call
  movq $0,   %rdi   // return number
  syscall

运行上述代码:

cpp hello.S > hello.i
as hello.i -o hello.o
ld hello.o
./a.out

系统调用追踪

我们可以通过 strace 来追踪一个应用程序在执行过程中产生的系统调用:

strace ./a.out

strace 是通过 ptrace() 这个系统调用来实现的,其可以在被追踪的程序每次进入或退出内核时停止其执行,然后追踪程序就可以进一步使用 ptrace() 来获取程序的状态。例如,下列代码给出了利用 ptrace() 实现的一个简易 tracer:

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

int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf(stderr, "Usage: %s <command>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  pid_t child = fork();
  if (child == 0) {
    // this process (tracee) is to be traced
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    execvp(argv[1], &argv[1]);
  } else {
    int status;
    struct user_regs_struct regs;
    while (1) {
      // wait for the tracee to stop itself
      waitpid(child, &status, 0);    
      // if the child exits
      if (WIFEXITED(status))
        break;
      // copy the tracee's general-purpose registers
      ptrace(PTRACE_GETREGS, child, NULL, &regs);
      printf("System call number: %lld\n", regs.orig_rax);
      // restart the stopped tracee, and arrange for the tracee to be stopped 
      // at the next entry to or exit from a system call
      ptrace(PTRACE_SYSCALL, child, NULL, NULL);
    }
  }
  return 0;
}