编写 thunk 来验证 SysV ABI 合规性

2023-12-07

The SysV ABI定义 Linux 的 C 级和汇编调用约定。

我想编写一个通用的 thunk 来验证函数是否满足被调用者保留寄存器的 ABI 限制,并且(可能)尝试返回一个值。

So given a target function like int foo(int, int) it's pretty easy3 to write such a thunk in assembly, something like1:

foo_thunk:
push rbp
push rbx
push r12
push r13
push r14
push r15
call foo
cmp rbp, [rsp + 40]
jne bad_rbp
cmp rbx, [rsp + 32]
jne bad_rbx
cmp r12, [rsp + 24]
jne bad_r12
cmp r13, [rsp + 16]
jne bad_r13
cmp r14, [rsp + 8]
jne bad_r14
cmp r15, [rsp]
jne bad_r15
ret

现在当然我实际上不想写一个单独的foo_thunk每次调用的方法,我只想要一个通用的方法。这个应该带有一个指向底层函数的指针(假设在rax),并且会使用间接调用call [rax] than call foo但否则会是一样的。

我不明白的是如何在 C 级别(或在 C++ 中,似乎有更多元编程选项 - 但我们在这里坚持使用 C)实现 thunk 的透明使用。我想采取类似的东西:

foo(1, 2);

并将其翻译为对thunk,但仍然在相同的位置传递相同的参数(这是 thunk 工作所必需的)。

预计我会修改源代码,也许使用宏或模板魔法,因此上面的调用可以更改为:

CHECK_THUNK(foo, (1, 2));

Giving the macro the name of the underlying function. In principle it could translate this to2:

check_thunk(&foo, 1, 2);

我怎样才能声明 check_thunk 呢?第一个参数是“某种类型”的函数指针。我们可以尝试:

check_thunk(void (*ptr)(void), ...);

因此,一个“通用”函数指针(所有指针都可以有效地转换为该指针,我们实际上只会将其称为汇编,在语言标准之外),加上可变参数。

但这不起作用:...与正确原型化的函数相比,具有完全不同的提升规则。它将适用于foo(1, 2)例如,但如果你打电话foo(1.0, 2)相反,varargs 版本只会将 1.0 保留为double你会打电话给foo具有完全错误的值(adouble值双关为整数。

上面还有将函数指针作为第一个参数传递的缺点,这意味着 thunk 不再按原样工作:它必须将函数指针保存在rdi某处,然后将所有值移一(即,mov rdi, rsi)。如果存在非寄存器参数,事情就会变得非常混乱。

有什么办法可以让这项工作顺利进行吗?

Note:这种类型的 thunk 基本上与堆栈上的任何参数传递都不兼容,这是这种方法的一个可接受的限制(它不应该用于具有那么多参数或带有多个参数的函数)MEMORY类参数)。


1 This is checks the callee preserved registers, but the other checks are similarly straightforward.

2 In fact, you don't even really need the macro for that - but it's also there so you can turn off the thunk in release builds and just do a direct call.

3 Well by "easy" I guess I mean one that doesn't work in all cases. The shown thunk doesn't correctly align the stack (easy to fix), and breaks if foo has any stack-passed arguments (significantly harder to fix).


以 gcc 特定的方式做到这一点的一种方法是利用typeof and 嵌套函数创建一个函数指针embeds对底层函数的调用,但本身没有任何参数。

该指针可以传递给 thunk 方法,该方法调用它并验证 ABI 合规性。

下面是一个将调用转换为int add3(int, int, int)使用这种方法:

原始调用如下所示:

int res = add3(a, b, c);

Then you wrap the call in a macro, like this2:

CALL_THUNKED(int res, add3, (a,b,c));

...它扩展到类似:

    typedef typeof(add3  (a,b,c)) ret_type; 

    ret_type closure() {              
        return add3  (a,b,c);         
    }                                 
    typedef ret_type (*typed_closure)(void);  
    typedef ret_type (*thunk_t)(typed_closure); 

    thunk_t thunk = (thunk_t)closure_thunk; 
    int res = thunk(&closure);

We create the closure() function on the stack, which calls directly into add3 with the original arguments. We can take the address of this closure and pass it an asm function without difficulty: calling it will have the ultimate effect of calling add3 with the arguments1.

其余的 typedef 基本上处理返回类型。我们只有一个closure_thunk方法,这样声明void* closure_thunk(void (*)(void));并在装配中实施。它需要一个函数指针(任何函数指针都可以转换为任何其他函数指针),但返回类型是“错误的”。我们把它投射到thunk_t这是动态生成的typedef对于具有“正确”返回类型的函数。

当然,这对于 C 函数来说肯定是不合法的,但是我们正在 asm 中实现该函数,因此我们有点回避这个问题(如果您想要更兼容一点,您也许可以向 asm 代码询问函数指针正确的类型,每次都可以“生成”它,超出了标准的范围:当然,它只是每次返回相同的指针)。

The closure_thunkasm 中的函数是按照以下方式实现的:

GLOBAL closure_thunk:function

closure_thunk:

push rsi
push_callee_saved

call rdi

; set up the function name
mov rdi, [rsp + 48]

; now check whether any regs were clobbered
cmp rbp, [rsp + 40]
jne bad_rbp
cmp rbx, [rsp + 32]
jne bad_rbx
cmp r12, [rsp + 24]
jne bad_r12
cmp r13, [rsp + 16]
jne bad_r13
cmp r14, [rsp + 8]
jne bad_r14
cmp r15, [rsp]
jne bad_r15

add rsp, 7 * 8
ret

也就是说,将我们要检查的所有寄存器压入堆栈(连同函数名),调用该函数rdi然后进行检查。这bad_*方法没有显示,但它们基本上会吐出一条错误消息,例如“函数 add3 覆盖了 rbp...顽皮!”和abort()的过程。

如果在堆栈上传递任何参数,这会中断,但它确实适用于在堆栈上传递的返回值(因为该情况的 ABI 传递一个指向“rax”中返回值位置的指针)。


1 How this is accomplished is kind of magic: gcc actually writes a few bytes of executable code onto the stack, and the closure function pointer points there. The few bytes basically loads a register with a pointer to the region that contains the captured variables (a, b, c in this case), and then calls the actual (read-only) closure() code which then can access the captured variables though that pointer (and pass them to add3).

2 As it turns out, we could probably use gcc's statement expression syntax to write the macro in a more usual function like syntax, something like int res = CALL_THUNKED(add3, (a,b,c)).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编写 thunk 来验证 SysV ABI 合规性 的相关文章

随机推荐

  • 如何将heroku 上回形针的上传目录更改为/tmp?

    我需要上传文件 然后使用回形针解析它们 目前它上传到 system文件夹中 这在heroku中是不允许的 我不需要持久上传 我解析它然后存储它们 所以我希望能够保存到 tmp 中然后解析 然后让它稍后被吹走 关于如何做到这一点的想法 如果我
  • 如何在R中两个单词之间的文本上进行gsub?

    EDIT 我想放置一个 n在我的文本中特定的未知单词之前 我知道这个未知词第一次出现在我的文本中将会在 树 和 湖 之间 前任 文本 text 1 TreeRULakeSunWater 2 A B C D EDIT 树 和 湖 永远不会改变
  • 地图不适用于加载的 Obj

    这是我之前的问题的延续here 我只是尝试向该对象的每一侧应用不同的纹理 但什么也没有出现 没有控制台错误 我相信我正在按正确的顺序应用事物 这应该很简单 但过去一个小时我一直在努力解决这个问题 下面是一个代码示例 function onL
  • Google Apps - 使用昵称发送电子邮件

    我在 google apps u 中有一个电子邮件帐户 电子邮件受保护 它有一个昵称 电子邮件受保护 我可以从 发送电子邮件吗 电子邮件受保护 多谢 确实 Google Apps 支持从您拥有的任何电子邮件地址 包括昵称 发送邮件 登录您的
  • 了解多重索引

    所以我在 csv 中有一个这样的示例数据集 name team date score John A 3 9 12 100 John B 3 9 12 99 Jane B 4 9 12 102 Peter A 9 9 12 103 Josie
  • 如何使用 Ember Data 查找模型?

    相当于什么 App Person find age 30 in the 新的 Ember 数据 IE 如何根据属性获取记录数组 ember data 1 0 0 beta2 中的等效方法现在是 this store find person
  • Java 中的 Context 到底是什么? [复制]

    这个问题在这里已经有答案了 我用 Google 搜索了这个并阅读了 Java 文档 但我有点困惑 有人可以解释一下什么是Context是简单的英语吗 用编程术语来说 它是较大的周围部分 可以具有any对当前工作单元的行为的影响 例如 使用的
  • 不处理条件渲染组件中的表单提交

    我有一个带有表单的自定义标记文件
  • 如何解码gzip数据?

    我有一个变量data 变量的类型为 TIdBytes 变量包含一些用 gzip 编码的数据 如何解码这些数据 如果您想手动解码数据 请查看DecompressGZipStream 的方法TIdCompressorZLib组件 或TDecom
  • 分子测试似乎忽略了ansible.cfg的remote_tmp设置

    我正在尝试使用molecule测试一个非常基本的角色 venv red jumphost docker ops cat roles fake role tasks main yml tasks file for fake role name
  • 在 ruby​​ 中从自身获取实例变量名称

    我有一个实例变量 foo我想写一些代码来获取字符串 foo 有什么提示吗 如果您拥有的只是对该对象的引用 那么您就无法真正干净利落地完成它 def foo bar something end def bar value value no c
  • DATA 和 IMAGE 填充在两行中。我想将它们绑定在一行中

    我要第二次问这个问题 我会把它说清楚 这样你就可以帮助我 好吧 让我们开始吧 我有一个注册流程 我把这个过程分成了两页 第一页仅用于 个人信息 然后 当我单击下一步按钮时 将出现下一页 postbackURL 此页面用于 上传照片 页面 他
  • Scrapy CLOSESPIDER_PAGECOUNT 设置无法正常工作

    我使用 scrapy 1 0 3 无法发现 CLOSESPIDER 扩展是如何工作的 对于命令 scrapy 抓取domain links set CLOSESPIDER PAGECOUNT 1 正确的是一个请求 但对于两页计数 scrap
  • javaFx 中更新 java-124 版本后如何解析 tableview 中的嵌套列?

    Exception in thread JavaFX Application Thread java lang NullPointerException at com sun javafx scene control skin Nested
  • 父类是否应该引用子类?

    早上好 我在工作中继承了一些遗留代码 它使用了一种相当不寻常的设计模式 我在论坛上找到的类似模式的唯一参考是here 情况是原始设计者有一个通用父类 不是抽象 它有一个直接引用子类的静态工厂方法 以下是这种编码风格的示例 可以在遗留代码的多
  • Google 的抓取工具可以解释 Javascript 吗?如果我通过 AJAX 加载页面怎么办? [关闭]

    Closed 这个问题是无关 目前不接受答案 当用户进入我的页面时 我必须进行另一个 AJAX 调用 以加载 div 内的数据 这就是我的应用程序的工作原理 问题是 当我查看这段代码的源代码时 它不包含该 AJAX 的源代码 当然 当我执行
  • InvalidCastException:无法将类型“System.DBNull”的对象转换为类型“System.Nullable`1[System.Int32]”[重复]

    这个问题在这里已经有答案了 我正在尝试从数据库执行存储过程 但是 我遇到了一个例外 InvalidCastException 无法将 System DBNull 类型的对象转换为 输入 System Nullable 1 System In
  • 为什么不调用复制构造函数?

    class MyClass public MyClass MyClass x 0 y 0 default constructor MyClass int X int Y x X y Y user defined constructor My
  • lex 和 yacc(符号表生成)

    我对编译器设计中的 lex 和 yacc 很陌生 我想知道符号表是在哪个阶段 词汇 句法或任何其他阶段 以及如何生成的 我可以简要描述一下 y output 文件 该文件是通过向 yacc 提供 v 选项生成的 我试图研究它 但没有得到太多
  • 编写 thunk 来验证 SysV ABI 合规性

    The SysV ABI定义 Linux 的 C 级和汇编调用约定 我想编写一个通用的 thunk 来验证函数是否满足被调用者保留寄存器的 ABI 限制 并且 可能 尝试返回一个值 So given a target function li