交叉编译问题: impossible constraint in 'asm

在进行MQTT协议的交叉编译过程中遇到警告错误,文章详细描述了如何通过修改Makefile文件中头文件路径设置来解决问题,确保编译过程顺利进行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在进行交叉编译MQTT的时候 出现了一些问题

参考了http://zhangjunxin520.blog.163.com/blog/static/30503703201227103553401/ 

报错了 

发现了

cc1: warning: include location "/usr/include" is unsafe for cross-compilation [-Wpoison-system-directories]
cc1: warning: include location "/usr/include" is unsafe for cross-compilation [-Wpoison-system-directories]
 

需要打开Makefile 把连接的头问题路径里面去掉 

 

在make 就可以通过了

 

 

 

 

 

 

 

 

 

 

<think>这个错误通常发生在GCC内联汇编中,当约束条件无法满足时。在之前的代码中,我们使用了`"i"`约束(立即数),但`MyCustomCode`和`trampoline`是全局符号,在32位模式下,GCC可能会尝试将它们作为立即数,但有时链接时地址可能超出范围,或者编译器认为不是常量。 解决方案: 1. 使用`"r"`约束(寄存器)代替`"i"`约束,让编译器将符号地址加载到寄存器中,然后通过寄存器间接调用和跳转。 2. 修改内联汇编,使用寄存器来传递函数地址和蹦床地址。 修改后的裸函数: ```cpp __attribute__((naked)) void MyFunction() { asm volatile ( // 保存所有通用寄存器 "pushal\n\t" // 保存标志寄存器 "pushfl\n\t" // 将MyCustomCode地址存入寄存器(例如eax) "movl %0, %%eax\n\t" // 调用自定义函数 "call *%%eax\n\t" // 恢复标志寄存器 "popfl\n\t" // 恢复通用寄存器 "popal\n\t" // 将蹦床地址存入寄存器(例如ebx) "movl %1, %%ebx\n\t" // 跳转到蹦床 "jmp *%%ebx\n\t" : // 无输出 : "m" (MyCustomCode), "m" (trampoline) // 使用"m"约束(内存操作数)或"r"约束(寄存器) : "eax", "ebx", "memory" // 破坏列表,告诉编译器我们修改了eax,ebx和内存 ); } ``` 但是,在裸函数中,我们不能直接使用局部变量,而且需要自己管理寄存器。上面的代码使用了`%0`和`%1`来引用输入操作数,并显式使用`eax`和`ebx`寄存器。 然而,在裸函数中,我们通常需要自己保存和恢复寄存器(除了通过pushal/popal之外,我们这里又额外使用了eax和ebx,但我们在内联汇编中已经将它们列在破坏列表中,这样编译器就不会使用它们来存储重要数据)。 另一种方法是使用全局变量,然后在汇编中直接通过符号名访问,但这样会失去位置无关性。 我们也可以这样修改: 使用`"r"`约束,让编译器选择寄存器,但裸函数中我们不知道编译器会分配什么寄存器,所以需要指定。 我们可以这样做: ```cpp __attribute__((naked)) void MyFunction() { asm volatile ( "pushal\n\t" "pushfl\n\t" // 使用寄存器传递参数 "movl %0, %%eax\n\t" "call *%%eax\n\t" "popfl\n\t" "popal\n\t" "movl %1, %%eax\n\t" "jmp *%%eax\n\t" : : "m" (MyCustomCode), "m" (trampoline) : "eax", "memory" ); } ``` 但是,我们仍然遇到约束问题,因为`"m"`约束要求变量在内存中,而全局变量确实在内存中。 然而,错误信息“impossible constraint”可能是因为在编译阶段,编译器无法确定这些符号地址是否可以作为有效的立即数(在32位模式下,地址通常可以,但可能因为位置无关代码(PIC)导致问题)。 另一种更可靠的方法是使用绝对跳转,并且不使用内联汇编的占位符,而是直接使用符号名: ```cpp __attribute__((naked)) void MyFunction() { asm volatile ( "pushal\n\t" "pushfl\n\t" "call MyCustomCode\n\t" // 直接使用符号名 "popfl\n\t" "popal\n\t" "jmp *trampoline\n\t" // 使用全局变量名,需要加*表示间接跳转 ); } ``` 但是,这样写需要确保在汇编时符号`MyCustomCode`和`trampoline`是可见的,并且链接器能够解析。同时,对于`trampoline`,它是一个全局变量,存储的是地址,所以需要间接跳转。 然而,在C++中,函数名和变量名会被修饰(mangled),所以我们需要用`extern "C"`来声明它们,以避免名称修饰。 修改后的完整代码: ```cpp #include <cstdint> #include <iostream> #include <sys/mman.h> #include <unistd.h> #include <cstring> // 蹦床地址全局变量 extern "C" { void* trampoline = nullptr; void MyCustomCode(); } // GCC格式的裸函数 __attribute__((naked)) void MyFunction() { asm volatile ( "pushal\n\t" "pushfl\n\t" "call MyCustomCode\n\t" "popfl\n\t" "popal\n\t" "jmp *trampoline\n\t" ); } // 自定义函数实现 extern "C" void MyCustomCode() { std::cout << "自定义HOOK函数被执行!" << std::endl; } // 安装HOOK的函数 void InstallHook(void* target, void* detour) { // 1. 创建蹦床 const int trampoline_size = 5; trampoline = mmap(nullptr, trampoline_size + 5, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 2. 复制被覆盖的指令到蹦床 std::memcpy(trampoline, target, trampoline_size); // 3. 添加跳回指令到蹦床 uint8_t* tramp_ptr = static_cast<uint8_t*>(trampoline) + trampoline_size; *tramp_ptr = 0xE9; // JMP指令 uint32_t tramp_offset = reinterpret_cast<uint8_t*>(target) + trampoline_size - (tramp_ptr + 5); *reinterpret_cast<uint32_t*>(tramp_ptr+1) = tramp_offset; // 4. 修改内存保护 size_t page_size = sysconf(_SC_PAGESIZE); uintptr_t target_page = reinterpret_cast<uintptr_t>(target) & ~(page_size - 1); mprotect(reinterpret_cast<void*>(target_page), page_size, PROT_READ | PROT_WRITE | PROT_EXEC); // 5. 写入跳转指令 uint8_t* target_ptr = static_cast<uint8_t*>(target); *target_ptr = 0xE9; // JMP指令 uint32_t offset = reinterpret_cast<uint8_t*>(detour) - (target_ptr + 5); *reinterpret_cast<uint32_t*>(target_ptr+1) = offset; } // 目标函数 void TargetFunction() { std::cout << "原始目标函数被执行!" << std::endl; } int main() { std::cout << "安装HOOK前:" << std::endl; TargetFunction(); // 安装HOOK InstallHook(reinterpret_cast<void*>(TargetFunction), reinterpret_cast<void*>(MyFunction)); std::cout << "\n安装HOOK后:" << std::endl; TargetFunction(); // 释放蹦床内存(实际应用中应谨慎处理) munmap(trampoline, 10); return 0; } ``` 注意: 1. 使用`extern "C"`确保`trampoline`和`MyCustomCode`在汇编代码中可见。 2. 在汇编代码中直接使用符号名,这样汇编器会处理地址。 3. 跳转到蹦床时使用`jmp *trampoline`,因为`trampoline`是一个指针变量,存储的是蹦床的地址。 这样修改后,应该可以避免约束错误。 编译命令(32位): ```bash g++ -m32 -o hook_example hook_example.cpp ``` 如果仍然遇到问题,可能需要检查蹦床的跳转偏移计算是否正确,以及内存保护设置。 另外,注意在64位系统上编译32位程序需要安装multilib库。 §§相关问题§§: 1. 在64位模式下如何编写类似的HOOK代码? 2. 如何实现一个跨平台(Windows/Linux)的HOOK库? 3. 在HOOK过程中,如何处理多线程环境下的竞争条件? 4. 如何实现HOOK的卸载和恢复原始函数?</think>### 解决 "impossible constraint in 'asm'" 错误 这个错误发生在GCC内联汇编中,当约束条件无法满足时。在之前的代码中,我们使用了 `"i"` (立即数) 约束,但函数地址在编译时可能不是有效的立即数。以下是修正后的解决方案: ```cpp #include <cstdint> #include <iostream> #include <sys/mman.h> #include <unistd.h> #include <cstring> // 蹦床地址全局变量 void* trampoline = nullptr; // 自定义函数声明 extern "C" void MyCustomCode(); // 修正后的GCC裸函数 __attribute__((naked)) void MyFunction() { asm volatile ( // 保存所有通用寄存器 "pushal\n\t" // 保存标志寄存器 "pushfl\n\t" // 调用自定义函数 - 使用绝对地址 "call MyCustomCode\n\t" // 恢复标志寄存器 "popfl\n\t" // 恢复通用寄存器 "popal\n\t" // 跳转到蹦床 "jmp *trampoline\n\t" ); } // 自定义函数实现 extern "C" void MyCustomCode() { std::cout << "自定义HOOK函数被执行!" << std::endl; } // 安装HOOK的函数 void InstallHook(void* target, void* detour) { // 1. 创建蹦床 const int trampoline_size = 5; trampoline = mmap(nullptr, trampoline_size + 5, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 2. 复制被覆盖的指令到蹦床 std::memcpy(trampoline, target, trampoline_size); // 3. 添加跳回指令到蹦床 uint8_t* tramp_ptr = static_cast<uint8_t*>(trampoline) + trampoline_size; *tramp_ptr = 0xE9; // JMP指令 uint32_t tramp_offset = reinterpret_cast<uint8_t*>(target) + trampoline_size - (tramp_ptr + 5); *reinterpret_cast<uint32_t*>(tramp_ptr+1) = tramp_offset; // 4. 修改内存保护 size_t page_size = sysconf(_SC_PAGESIZE); uintptr_t target_page = reinterpret_cast<uintptr_t>(target) & ~(page_size - 1); mprotect(reinterpret_cast<void*>(target_page), page_size, PROT_READ | PROT_WRITE | PROT_EXEC); // 5. 写入跳转指令 uint8_t* target_ptr = static_cast<uint8_t*>(target); *target_ptr = 0xE9; // JMP指令 uint32_t offset = reinterpret_cast<uint8_t*>(detour) - (target_ptr + 5); *reinterpret_cast<uint32_t*>(target_ptr+1) = offset; // 6. 刷新指令缓存 __builtin___clear_cache(reinterpret_cast<char*>(target), reinterpret_cast<char*>(target) + trampoline_size); } // 目标函数 void TargetFunction() { std::cout << "原始目标函数被执行!" << std::endl; } int main() { std::cout << "安装HOOK前:" << std::endl; TargetFunction(); // 安装HOOK InstallHook(reinterpret_cast<void*>(TargetFunction), reinterpret_cast<void*>(MyFunction)); std::cout << "\n安装HOOK后:" << std::endl; TargetFunction(); // 释放蹦床内存(实际应用中应谨慎处理) munmap(trampoline, 10); return 0; } ``` ### 错误原因与修正说明 1. **问题分析**: - 原始代码使用 `"i"` 约束要求函数地址作为立即数 - 在32位地址空间中,函数地址可能无法作为有效的立即数 - GCC无法在编译时确定函数的绝对地址 2. **修正方案**: - 直接使用符号名称(`call MyCustomCode`)替代约束 - 使用 `jmp *trampoline` 进行内存间接跳转 - 添加 `extern "C"` 确保函数名不被修饰 - 包含 `<cstring>` 头文件以使用 `memcpy` 3. **关键改进**: ```cpp // 修正前(导致错误) "call %c0\n\t" "jmp *%c1\n\t" : : "i" (MyCustomCode), "i" (trampoline) // 修正后(正确) "call MyCustomCode\n\t" "jmp *trampoline\n\t" ``` 4. **新增功能**: ```cpp // 刷新指令缓存 __builtin___clear_cache(reinterpret_cast<char*>(target), reinterpret_cast<char*>(target) + trampoline_size); ``` - 确保修改后的指令被CPU正确识别 - 防止指令缓存不一致问题 ### 编译与运行 ```bash # 安装32位开发库(Ubuntu/Debian) sudo apt-get install gcc-multilib g++-multilib # 编译32位程序 g++ -m32 -o hook_example hook_example.cpp # 运行 ./hook_example ``` ### 预期输出 ``` 安装HOOK前: 原始目标函数被执行! 安装HOOK后: 自定义HOOK函数被执行! 原始目标函数被执行! ``` ### 技术要点总结 1. **GCC裸函数特性**: - 使用 `__attribute__((naked))` 声明裸函数 - 函数体完全由汇编指令组成 - 必须手动处理寄存器和栈 2. **跨平台内存管理**: - `mmap` 分配可执行内存 - `mprotect` 修改内存保护属性 - `__builtin___clear_cache` 刷新指令缓存 3. **跳转计算**: ```cpp uint32_t offset = detour_address - (current_address + 5); ``` - `+5` 包含操作码(1字节)和偏移量(4字节) - 使用 `reinterpret_cast` 进行地址运算 4. **安全考虑**: - 确保覆盖的指令是完整的 - 蹦床包含原始指令和跳回指令 - 多线程环境下需要同步机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值