ucontext

setjmp/longjmp的最终演变

  1. setjmp()/longjmp()
  2. sigsetjmp()/siglongjmp()
  3. getcontext()/makecontext()/setcontext()/swapcontext()

1. setjmp()/longjmp()

  • 通过setjmp()设置一个保存点,可以通过longjmp()返回到那里
  • 实现了函数间的goto
  • 只要不从setjmp()的函数退出就有效
    • 返回保存点时,esp寄存器会还原到设置保存点时的值。即只返回stack位置,而不是内容。调用setjmp()的函数一旦退出,stack本身可能与保存点设定时不同,即便还原也可能不能正常工作
  • 正常工作的case:
    • esp还原时,stack frame也还原到正常
  • 非正常工作的case:
    • funcA退出,funcC,funcD调用后,funcD内longjmp(),esp还原,这时候stack frame是funcC,不是原本的funcA

2. sigsetjmp()/siglongjmp()

也可以恢复signal stack信息

  • 假如存在一个通过sigaction(2)指定signal stack的signal handler(例如SIG_TERM的handler运行中,不接受SIG_ALRM),当这个handler内通过longjmp()返回保存点时,它不接受SIG_ALRM返回。为避免这种情况,需要一种在longjmp()时还原signal stack信息的处理机制

3. getcontext()/makecontext()/setcontext()/swapcontext()

  • 通过getcontext()设置保存点(context)
  • 通过 makecontext()将context与函数相关联
    • 恢复时,可以从关联的函数进行(带参数)
    • 但是需要给context一个独立的stack区域

      • 只在这种情况下,与setjmp()/sigsetjmp()不同,调用getcontext()的函数退出后仍然有效
    • 也可以指定next context
      • 当这个函数结束时,自动转换到next
  • 通过setcontext()来返回到那个context
  • 通过swapcontext()来返回到那个context,并且保存现在的context

ucontext

  1. 生成context
  2. 根据需要修改context中的信息
    • 通过makecontext(),可以修改为通过特定函数带参数调用
    • 可以直接重写context中的寄存器信息
  3. 之后通过setcontext()/swapcontext()进行切换
    • 可以通过适当设置,从任意寄存器状况恢复

context的结构

  • 作为参考,context结构大概是这样(Ubuntu x86_64)
  • ss_sp是指向新stack的指针
  • ss_size是它的size
  • gregs[0~22]是用来保存通用寄存器及其值
  • 从gregs[0]开始按顺序,R8, R9, R10, R11, R12, R13, R14, R15,RDI, RSI, RBP, RBX, RDX, RAX, RCX, RSP, RIP, EFL, CSGSFS(+pad),ERR, TRAPNO, OLDMASK, CR2
  • __val是signal mask

ucontext与sigaction

context与sigaction也有关联

  • https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigaction.2.html
  • 在sa_flags指定SA_SIGINFO时(不是sa_handler),根据sa_sigaction的signum指定对应的signal handler函数。指定的函数,第一个参数是signal 编号,第二个是指向siginfo_t的指针,第三个参数是经过(void * 类型转换)的指向ucontext_t的指针
  • 只要正确设置,成为signal handler的函数也可以接受context作为第三个参数

ucontext的总结

  • 非常强大的机制
    • 使用时,可以从任意函数/地址启动任意的寄存器状态
    • 利用这个功能,用户可以自己实现类似线程的context切换
    • 但是,context的结构非常复杂
    • 需要注意,没有system call

DWARF

  • 之前解释过malloc(-1)的行为
    • 只是失败
  • new(-1)时,会产生std::bad_alloc
    • 产生异常,需要手动处理
    • 实际上尝试大概是这样
  • 可以使用try-catch来手动处理:

那么try-catch是怎么避免异常进行还原的?

  • 使用IDA能够看到try catch,但不能确定范围

这是libgcc通过DWARF来确定处理的位置

DWARF

  • call frame信息
  • CIE就像一个初期状态模板
  • 也有readelf -wF不会显示的信息
    • 是LSDA
  • 可以使用katana

katana

相关论文及题目