考虑一下这完全愚蠢的代码:
int main() { __asm__("int $0x2"); }这会导致运行时出现分段错误。2是英特尔IDT中NMI的代码(第6.3.1节这里)。
我很好奇为什么会有这样的断层呢?到底什么是控制流,它最终会导致分段故障?
并在此贴上手册第6.3.3节:
6.3.3软件生成的中断 INT n指令通过提供中断向量号作为操作数,允许从软件内部产生中断。例如,INT 35指令强制对中断处理程序的中断35进行隐式调用。从0到255的任何中断向量都可以用作此指令中的参数。但是,如果使用处理器的预定义NMI向量,则处理器的响应将不同于以正常方式生成的NMI中断的响应。如果在此指令中使用向量号2( NMI向量),则调用NMI中断处理程序,但处理器的NMI处理硬件未被激活。在带有INT指令的软件中生成的中断不能被EFLAGS寄存器中的IF标志所掩盖。
发布于 2019-12-09 18:36:18
idt中的门包含一个描述符特权级别(DPL),它是允许调用此条目的最大调用方权限级别(CPL)。真正的NMI是由cpu上的电信号引起的,它提供了一个人为的CPL为0。这样,内核就不必区分真实信号和假信号。
通过int调用的系统服务将有一个数字更大的DPL,以允许指令用指令打开门。根据内核的不同,int 3(断点)、4(溢出)和5(界)可能分别用作直接操作码,以方便调试、“入”和“界”操作码。
发布于 2019-12-09 19:07:21
你发现了一个内核错误。您的程序试图执行禁止用户空间程序的CPU操作(int 2),而不是无效的内存访问。因此,它应该发送一个SIGILL (非法指令)信号,而不是一个SIGSEGV信号。
这个错误的原因可能是这个特定的禁止操作是用"#GP错误“而不是"#UD错误”(用x86体系结构手册使用的术语)报告给操作系统的。#GP错误也用于报告无效的内存访问,编写代码将其映射到信号的人不必费心区分“实际无效内存访问”和“不正确地使用#GP报告的int”。我在Linux和NetBSD上也观察到了这个错误,所以这肯定是一个很容易犯的错误。
在调试涉及信号的问题时,通常需要为麻烦的信号建立一个信号处理程序,在标志中使用带有sigaction的SA_SIGINFO。设置SA_SIGINFO时,处理程序将收到两个额外的参数,这些参数提供有关信号的详细信息。您不必在信号处理程序中使用这些参数;而是在调试器下运行程序,允许发送信号,然后检查调试器中的详细信息。下面是对您的程序所做的修改:
#include <signal.h>
#include <unistd.h>
#include <ucontext.h>
void handler(int s, siginfo_t *si, void *uc)
{
pause();
}
int main(void)
{
struct sigaction sa;
sa.sa_sigaction = handler;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGBUS, &sa, 0);
sigaction(SIGFPE, &sa, 0);
sigaction(SIGILL, &sa, 0);
sigaction(SIGSEGV, &sa, 0);
sigaction(SIGSYS, &sa, 0);
sigaction(SIGTRAP, &sa, 0);
asm("int $0x2");
}( uc参数是指向ucontext_t的指针,但该类型是在<ucontext.h>中声明的,而不是在<signal.h>中声明的,因此规范要求您必须定义处理程序才能接受第三个void *类型的参数,然后如果要使用它,则必须对其进行转换。)
我为所有对应于致命的同步CPU异常的信号设置了处理程序,因为为什么不呢?pause只是在处理程序中无限期地停止执行,所以我可以点击control进入调试器,信号帧就可用了。
下面是我在Linux上得到的信息:
(gdb) bt
#0 0x00007ffff7eb4af4 in __libc_pause ()
at ../sysdeps/unix/sysv/linux/pause.c:29
#1 0x000055555555516d in handler (s=11, si=0x7fffffffd830, uc=0x7fffffffd700)
at test.c:5
#2 <signal handler called>
#3 main () at test.c:14
(gdb) frame 1
#1 0x000055555555516d in handler (s=11, si=0x7fffffffd830, uc=0x7fffffffd700)
at test.c:5
5 pause();
(gdb) p *si
$1 = {si_signo = 11, si_errno = 0, si_code = 128, __pad0 = 0, _sifields = {
_pad = {0 <repeats 28 times>}, _kill = {si_pid = 0, si_uid = 0}, _timer = {
si_tid = 0, si_overrun = 0, si_sigval = {sival_int = 0,
sival_ptr = 0x0}}, _rt = {si_pid = 0, si_uid = 0, si_sigval = {
sival_int = 0, sival_ptr = 0x0}}, _sigchld = {si_pid = 0, si_uid = 0,
si_status = 0, si_utime = 0, si_stime = 0}, _sigfault = {si_addr = 0x0,
si_addr_lsb = 0, _bounds = {_addr_bnd = {_lower = 0x0, _upper = 0x0},
_pkey = 0}}, _sigpoll = {si_band = 0, si_fd = 0}, _sigsys = {
_call_addr = 0x0, _syscall = 0, _arch = 0}}}
(gdb) p *(ucontext_t *)uc
$2 = {uc_flags = 7, uc_link = 0x0, uc_stack = {ss_sp = 0x0, ss_flags = 0,
ss_size = 0}, uc_mcontext = {gregs = {0, 0, 8, 582, 93824992235632,
140737488346656, 0, 0, 11, 140737488345936, 140737488346432, 0, 0, 0,
140737352200658, 140737488346272, 93824992235964, 66050,
12103423998558259, 18, 13, 0, 0}, fpregs = 0x7fffffffd8c0,
__reserved1 = {0, 1, 140737354129808, 140737488345320, 140737353799024,
140737354129808, 8455580781, 140737354130672}}, uc_sigmask = {__val = {
0, 11, 128, 0 <repeats 13 times>}}, __fpregs_mem = {cwd = 0, swd = 0,
ftw = 0, fop = 0, rip = 140737488346656, rdp = 0, mxcsr = 895,
mxcr_mask = 0, _st = {{significand = {0, 0, 0, 0}, exponent = 0,
__glibc_reserved1 = {0, 0, 0}}, {significand = {8064, 0, 65535, 0},
exponent = 0, __glibc_reserved1 = {0, 0, 0}}, {significand = {0, 0, 0,
0}, exponent = 0, __glibc_reserved1 = {0, 0, 0}}, {significand = {0,
0, 0, 0}, exponent = 0, __glibc_reserved1 = {0, 0, 0}}, {
significand = {0, 0, 0, 0}, exponent = 0, __glibc_reserved1 = {0, 0,
0}}, {significand = {0, 0, 0, 0}, exponent = 0, __glibc_reserved1 = {
0, 0, 0}}, {significand = {0, 0, 0, 0}, exponent = 0,
__glibc_reserved1 = {0, 0, 0}}, {significand = {0, 0, 0, 0},
exponent = 0, __glibc_reserved1 = {0, 0, 0}}}, _xmm = {{element = {0,
0, 0, 0}} <repeats 16 times>}, __glibc_reserved1 = {
0 <repeats 18 times>, 1179670611, 836, 7, 0, 832, 0}}, __ssp = {0, 0, 0,
3}}siginfo_t结构基本上是无用的;它有si_code == 128,这意味着“这个信号是由内核生成的,但是我们不会告诉您关于它的任何其他信息”,所有其他字段都是零。我认为这是另一个内核错误。
ucontext_t结构更有用,特别是
(gdb) p/x ((ucontext_t *)uc)->uc_mcontext.gregs[REG_RIP]
$3 = 0x5555555551bc这是导致信号的指令的地址。如果我拆开main..。
(gdb) disas main
...
0x00005555555551b7: callq 0x555555555030 <sigaction@plt>
0x00005555555551bc: int $0x2
0x00005555555551be: mov $0x0,%eax
0x00005555555551c3: leaveq
0x00005555555551c4: retq ..。我看到产生信号的指令确实是int $0x2。
在NetBSD上,我得到了一些稍微不同的东西:
(gdb) p *si
$1 = { si_pad = "[garbage]", _info = {
_signo = 11, _code = 2, _errno = 0, _pad = 0, _reason = {
_rt = {_pid = -146410395, _uid = 32639, _value = {sival_int = 4,
sival_ptr = 0x4}}, _child = {_pid = -146410395, _uid = 32639,
_status = 4, _utime = 0, _stime = 0}, _fault = {
_addr = 0x7f7ff745f465 <__sigemptyset14>, _trap = 4, _trap2 = 0,
_trap3 = 0}, _poll = {_band = 140187586131045, _fd = 4}}}}这个siginfo_t实际上已经填好了。SIGSEGV的si_code 2是SEGV_ACCERR (“映射对象的无效权限”),这不是胡说八道。头或手册中没有足够的信息让我理解_trap = 4的含义,或者为什么_addr指向C库中的某个地址,而且我不想在今天跳转NetBSD内核。;-)
此外,出于我今天不想调查的原因,NetBSD上的gdb无法访问ucontext_t的定义(尽管我明确地包含了ucontext.h),所以我不得不原始地将其转储出去:
(gdb) p *(ucontext_t *)uc
No symbol "ucontext_t" in current context.
(gdb) x/40xg uc
0x7f7fffffd7b0: 0x00000000000a000d 0x0000000000000000
0x7f7fffffd7c0: 0x0000000000000000 0x0000000000000000
0x7f7fffffd7d0: 0x0000000000000000 0x0000000000000000
0x7f7fffffd7e0: 0x0000000000000000 0x0000000000000005
0x7f7fffffd7f0: 0x00007f7fffffdb50 0x0000000000000000
0x7f7fffffd800: 0x00007f7ff7483a0a 0x0000000000000002
0x7f7fffffd810: 0x000000000000000d 0x00007f7ff749f340
0x7f7fffffd820: 0x0000000000000246 0x00007f7fffffdb90
0x7f7fffffd830: 0x00007f7ffffffdea 0x00007f7ff511a4c0
0x7f7fffffd840: 0x00007f7ffffffdea 0x00007f7fffffdb70
0x7f7fffffd850: 0x00007f7fffffffe0 0x0000000000000000
0x7f7fffffd860: 0x0000000000000000 0x0000000000000000
0x7f7fffffd870: 0x000000000000003f 0x00007f7ff748003f
0x7f7fffffd880: 0x0000000000000004 0x0000000000000012
0x7f7fffffd890: 0x0000000000400af5 0x000000000000e033 <---
0x7f7fffffd8a0: 0x0000000000010246 0x00007f7fffffdb50
0x7f7fffffd8b0: 0x000000000000e02b 0x00007f7ff7ffd0c0
0x7f7fffffd8c0: 0x000000000000037f 0x0000000000000000
0x7f7fffffd8d0: 0x0000000000000000 0x0000ffbf00001f80
0x7f7fffffd8e0: 0x0000000000000000 0x0000000000000000
(gdb) disas main
Dump of assembler code for function main:
...
0x0000000000400af0 <+166>: callq 0x400810 <__sigaction14@plt>
0x0000000000400af5 <+171>: int $0x2
0x0000000000400af7 <+173>: leaveq
0x0000000000400af8 <+174>: retquc指出的与程序文本有任何对应关系的内存区域中唯一的地址是0x0000000000400af5,也就是int指令的地址。
https://stackoverflow.com/questions/59254571
复制相似问题