c - 如何从上下文中手动递增指令指针?

标签 c assembly x86 segmentation-fault

首先,让我说,我在这里做的事情大多数人都没有正当理由去做。 99.99...% 的所有段错误都应该导致明确终止,并且在除了最简单的情况之外的任何情况下愉快地处理它们将导致非常糟糕的行为和损坏的堆栈。如果您来这里是为了解决段错误,请查看以下链接:https://www.securecoding.cert.org/confluence/display/seccode/SIG35-C.+Do+not+return+from+a+computational+exception+signal+handler

就是说,我正致力于根据外部标准实现一个环境,该标准定义了从计算逻辑错误的信号处理程序返回的行为是向前跳过一条指令。我知道这很糟糕,但是我无法控制它;我不能简单地修改定义,因为它是针对已经编写了其他软件元素的嵌入式系统,这些软件元素取决于定义的行为(它们通常对安全至关重要,并且需要能够优雅地退出,即使它们不优雅或糟糕的事情;此外,我没有源代码,所以我不能只修复段错误,任何现有的错误段错误/崩溃行为实际上都是需要的,因为我正在模拟现有系统的行为)。

虽然系统本身是在具有固定指令长度的 PowerPC 上运行,但我们的开发是在指令不是固定长度的并行 x86/x64 环境中进行的。我知道以下代码可以工作,尽管对于 x86 来说效果不佳:

#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <ucontext.h>
#include <sys/mman.h>

#define CRASHME *((int*)NULL) = 0 
//for x86
#ifdef REG_EIP
#define INCREMENT(x) (x)->uc_mcontext.gregs[REG_EIP]++
//for x64
#elif defined REG_RIP
#define INCREMENT(x) (x)->uc_mcontext.gregs[REG_RIP]++
//for PPC arch
#elif defined PT_NIP
#define INCREMENT(x) (x)->uc_mcontext.uc_regs->gregs[PT_NIP]+=4
#endif

static void handler(int sig, siginfo_t *si, void *vcontext)
{
    ucontext_t *context = (ucontext_t *)vcontext;
    INCREMENT(context);
}

void crashme_function(void)
{
    printf("entered new context, segfaulting!\n");
    CRASHME;
    printf("SEGFAULT handled!\n");
}

int main (int argc, char* args)
{
    struct sigaction sa;
    printf("Printing a thing\n");
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    sigaction(SIGSEGV, &sa, NULL);
    printf("Entering new context...\n");
    crashme_function();
    printf("context exited successfully\n");
    return(0);

}

在运行Linux内核3.11.X的基于intel的arch上,这段代码的执行结果会将指令指针前进1,最终会前进到指令之外。我知道这可能不适用于所有说明。事实上,当在我的测试环境中执行时,处理程序进入 6 次(对于指令的 6 个字节),然后继续执行 CRASHME。

在给定现有指令的情况下,仅将给定的指令指针前进到下一条指令似乎是一项微不足道的任务;处理器每个周期都这样做。在其他设置中,有人说“查看指令表并构建自己的”或“实现反汇编程序”。这些对于这项任务既不合适也不必要,因为两者都已经由其他人完成并发布(几乎?)专门在我的工作计算机无法访问的网络位置,并且我不相信将我的家提交给这些地方个人电脑。但是,我在哪里可以找到这样的表或库来完成指令计算,而无需查看我已经知道我无法访问的站点?

最佳答案

Linux 内核源代码具有 X86 操作码映射的编码,然后由 Awk 脚本解析该映射以生成一组可用于读取指令的表。它有足够的信息来为您提供准确的指令大小,尽管您可能需要扩展它以包含有关浮点指令和一些较新的 Intel 扩展(例如 AVX)的信息。

如果您有权访问 linux 内核源代码树,请查看 arch/x86/lib/x85-opcode-map.txt。

其中包含确定指令大小所需的所有数据。

有一个 AWK 脚本 @arch/x86/tools/gen-insn-attr-x86.awk 将读取操作码文件并生成一系列表,这些表对操作码映射中的信息进行编码。

最后,如果您查看 arch/x86/lib/insn.c,其中有一个函数 insn_get_length(...) 会使用从操作码映射生成的表为您提供指令的长度。这应该足以让您回答您的特定问题“这条指令有多大”。

该代码没有什么特别“内核”的地方。您无需执行任何特殊操作即可适应用户模式。

我假设访问 Linux 内核源代码对您来说不应该是一个安全问题,并且没有什么阻碍您阅读/采用 GPL 代码。

关于c - 如何从上下文中手动递增指令指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20908227/

相关文章:

c - 尝试使用 C 中的 for 循环制作此星形输出

c - 第一种使用枚举的编程语言是什么?

Java 操作系统问题

assembly - 我可以在dosbox中运行切换到保护模式的汇编程序吗?

performance - 预取指令是否需要在退出之前返回其结果?

assembly - 为什么间接寻址中的索引器必须是双字?

CMake;第386章 :x86-64 architecture of input file (. ) 与 i386 输出不兼容

C - Arduino - 无法将 'uint8_t*' 转换为 'uint16_t*'

assembly - 为什么 rbp 和 rsp 被称为通用寄存器?

c - 为什么使用 .data 而不是在 .bss 中保留空间并在运行时初始化,用于 assembly/C 中的变量?