我正在尝试编写一个复制函数的函数(并最终修改其程序集)并返回它。这适用于一个级别的间接,但在两个我得到段错误。
这是一个最小(非)工作示例:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#define BODY_SIZE 100
int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }
int (*g(void))(void) {
void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(r, f, BODY_SIZE);
return r;
}
int (*(*h(void))(void))(void) {
void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(r, g, BODY_SIZE);
return r;
}
int main() {
printf("%d\n", f());
printf("%d\n", G()());
printf("%d\n", g()());
printf("%d\n", H()()());
printf("%d\n", h()()()); // This one fails - why?
return 0;
}
我可以记忆到一个mmap的区域,创建一个可以调用的有效函数(g()()
)。但是,如果我再次尝试应用它(h()()()
),则会出现段错误。我已经确认它正确创建了g
的复制版本,但是当我执行该版本时,我得到了一个段错误。
我有什么理由不能在另一个mmap的区域中的一个mmap区域中执行代码吗?从使用x/i
检查的探索性gdb开始,我似乎可以成功调用,但是当我返回我来自的函数时已被删除并替换为0。
如何让这种行为发挥作用?它甚至可能吗?
BIG EDIT:
很多人都问我的理由,因为我在这里显然是在做XY问题。这是真实而有意的。你看,一个月之前有一点this问题发布在代码高尔夫堆栈交换上。对于C / Assembly解决方案,它也有一个很好的bounty。我对这个问题做了一些空洞的思考,并意识到通过复制一个函数体同时删除一个具有一些唯一值的地址,我可以在其内存中搜索该值并将其替换为有效的地址,从而允许我有效地创建lambda函数将单个指针作为参数。使用这个我可以得到单一的currying工作,但我需要更一般的currying。因此,我当前的部分解决方案与here相关联。这是展示我试图避免的段错误的完整代码。虽然这几乎是一个坏主意的定义,但我觉得它很有趣,并且想知道我的方法是否可行。我唯一缺少的是运行从函数创建的函数的能力,但我无法让它工作。
答案 0 :(得分:5)
代码使用相对调用来调用mmap
和memcpy
,因此复制的代码最终会调用无效位置。
您可以通过指针调用它们,例如:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#define BODY_SIZE 100
void* (*mmap_ptr)(void *addr, size_t length, int prot, int flags,
int fd, off_t offset) = mmap;
void* (*memcpy_ptr)(void *dest, const void *src, size_t n) = memcpy;
int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }
int (*g(void))(void) {
void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy_ptr(r, f, BODY_SIZE);
return r;
}
int (*(*h(void))(void))(void) {
void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy_ptr(r, g, BODY_SIZE);
return r;
}
int main() {
printf("%d\n", f());
printf("%d\n", G()());
printf("%d\n", g()());
printf("%d\n", H()()());
printf("%d\n", h()()()); // This one fails - why?
return 0;
}
答案 1 :(得分:3)
我正在尝试编写一个复制函数的函数
我认为这不是正确的方法,除非你非常了解你的平台的机器代码(然后你不会问这个问题)。请注意position independent code(有用,因为一般mmap(2)会使用ASLR并在地址中提供一些“随机性”。 BTW,真正的self-modifying machine code(即更改某些现有有效机器代码的某些字节)今天cache和branch-predictor不友好,应该在实践中避免使用。
我建议采用两种相关方法(选择其中一种方法)。
生成一些临时 C文件(另请参阅this),例如在/tmp/generated.c
中,然后将使用gcc -Wall -g -O -fPIC /tmp/generated.c -shared -o /tmp/generated.so
的汇编分为plugin,然后dlopen(3)(dynamic loading} /tmp/generated.so
{{3插件(并且可能使用shared object来查找其中的函数指针...)。有关共享对象的更多信息,请阅读Drepper的dlsym(3)论文。今天,你可以dlopen
成千上万的这样的共享库(参见我的How To Write Shared Libraries示例)和C编译器(比如最近的manydl.c)足够快,可以编译几千行代码在与交互相容的时间内(例如,小于十分之一秒)。生成C代码是GCC实践。实际上,在发出之前,你会在生成的C代码的内存中代表一些widely used。
使用一些AST库,例如JIT compilation或GCCJIT,或LLVM,或libjit等....会在内存中生成一个函数,执行所需的asmjit,然后给你一些指针。
S-expr和closures的概念值得了解。阅读callbacks和SICP(当然还有Lisp In Small Pieces,以了解一般编译器文化)。
答案 2 :(得分:0)
此问题发布在代码golf.SE
上
我更新了the 8086 16-bit code-golf answer关于问题总结的问题以包含评论的反汇编。
您可以在32位代码中使用相同的想法,使用stack-args调用约定来生成机器代码函数的修改副本,该函数适用于push imm32
。但它不再是固定大小的,因此您需要更新复制的机器代码中的函数大小。
在普通的通话约定中,第一个arg被推送到最后,因此您无法在固定大小push imm32
/ {{之前追加另一个call target
1}} / leave
预告片。如果编写纯asm答案,则可以使用备用调用约定,其中args以其他顺序推送。或者你可以有一个固定大小的介绍,然后是一个不断增长的ret
+呼叫/离开/返回序列。
currying函数本身可以使用register-arg调用约定,即使你希望目标函数以i386 System V为例(stack args)。
您肯定希望通过不支持超过32位的args来简化,因此没有值的结构,也没有push imm32
。 (当然你可以链接多次调用currying函数来构建一个更大的arg。)
考虑到编写新代码 - 高尔夫挑战的方式,我猜你要比较curried args的总数与目标&#34;输入&#34;的args数量。功能需要。
我认为没有机会只用double
就可以在纯C中完成这项工作;你必须修改机器代码。