【发布时间】:2014-01-21 09:26:06
【问题描述】:
首先,让我说我在这里做的事情大多数人都没有正当理由去做。 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 的架构上将指令指针推进 1,并最终退出指令。我知道这可能不适用于所有说明。事实上,当在我的测试环境中执行时,处理程序进入了 6 次(对于指令的 6 个字节),然后继续执行超过 CRASHME。
在给定现有指令的情况下,仅将给定指令指针前进到下一条指令似乎是一项微不足道的任务;处理器每个周期都会这样做。在其他设置中,有人说“查看指令表并构建自己的”或“实现反汇编程序”。这些对于任务来说既不合适也没有必要,因为两者都已经由其他人完成并且仅发布在(几乎?)我的工作计算机无法访问的网络位置,并且我不相信提交我的家个人电脑。但是我在哪里可以找到这样的表或库来完成只进行指令计算,而不查看我已经知道我无法访问的站点?
【问题讨论】:
-
如果你甚至不知道那里有哪种指令,你会用它实现什么。您还应该知道指令可以重叠,因此如果您一个接一个地增加 1 个字节,您只会遇到可以解释为指令流的“一些”字节组合。如果出现异常,您甚至都不知道指令指针在哪里,因为如果最后一条指令是
ret,堆栈上有一些损坏的值,那么它可以在您的地址空间中的任何位置。 -
这就是我问这个问题的原因:我知道我编写的代码在任何 x86/x64 架构上都非常不可信。这就是为什么我需要知道如何推进任意 IP。我希望提高 x86/x64 的 INCREMENT(x) 的复杂性。另外,这不是我的问题。我不是这里的用户,我是系统。用户已经知道这是行为,他们有责任从用户实现的错误处理程序中终止进程。
-
你应该反汇编EIP指向的位置。不。我会跳过有问题的例程中的所有代码,只需遍历堆栈到调用函数(跳转到执行返回的已知位置)。
-
完全没有建设性的评论:说一个人不应该回到段错误代码,因为 CERT 不建议这样做,就像说一个人不应该放火烧房子,因为吸烟会导致癌症。并不是说 CERT 不应该将其与“不要写
if (x) …”和“不要写if (x==0)”列表一起列出。 -
这就是我写整个第二段的原因。我不能穿过堆栈到达调用者函数,也不能跳转。我必须转到下一条指令,因为这是系统参考文档的现有定义,也是根据 CERT,这是专门编写的,因为一大群早期程序员发现 SEGFAULTS 可以用信号处理程序处理并着手编写代码处理而不是崩溃,而不是解决实际的段错误(这几乎总是正确的解决方案)。在大多数操作系统中,从 SIGSEGV 返回是未定义的,这是有原因的。
标签: c assembly x86 segmentation-fault