This article only analyzes the changes in the user stack and kernel stack after the signal is sent to the user program. Without analyzing real-time signals, the entire process is basically the same. Many references <scenario analysis>, so some code may be different from the current kernel, such as RESTORE_ALL, but the general mechanism is similar.
1. A small signal example
Hex @ Gentoo ~ /Signal $ cat sigint. c
# Include <stdio. h>
# Include <stdlib. h>
# Include <signal. h>
Void sig_int (int signo)
{
Printf ("hello \ n ");
}
Int main ()
{
If (signal (SIGINT, sig_int) = SIG_ERR ){
Printf ("can't catch SIGINT \ n ");
Exit (-1 );
}
For (;;)
;
Return 0;
}
2. What happened in the user Stack
2.1 compile and run the program, set the breakpoint to start with the sig_int function (0x80482e8), and set the processing method of the SIGINT signal.
Hex @ Gentoo ~ /Signal $ gdb./sigint
(Gdb) B * 0x80482e8
Breakpoint 1 at 0x80482e8: file sigint. c, line 6.
(Gdb) handle SIGINT noprint pass
SIGINT is used by the debugger.
Are you sure you want to change it? (Y or n) y
Signal Stop Print Pass to program Description
SIGINT No Yes Interrupt
(Gdb) r
Starting program:/home/gj/signal/sigint
2.2 send a signal to the program: kill-INT pid Number of the program
Hex @ Gentoo ~ /Signal $ kill-INT 4639
2.3 The program stops at the breakpoint after receiving the signal
Breakpoint 1, sig_int (signo = 2) at sigint. c: 6
6 {
(Gdb) I r esp
Esp 0xbfffe7ec 0xbfffe7ec
(Gdb) x/40a 0xbfffe7ec
0xbfffe7ec: 0xb7fff400 0x2 0x33 0x0
0xbfffe7fc: 0x7b 0x7b 0x8048930 <__libc_csu_init> 0x80488f0 <__libc_csu_fini>
0xbfffe80c: 0xbfffed58 0xbfffed40 0x0x0
0xbfffe81c: 0xbfffec18 0x0 0x0x0
0xbfffe82c: 0x8048336 <main + 58> 0x73 0x213 0xbfffed40
0xbfffe83c: 0x7b 0xbfffead0 0x0x0
0xbfffe84c: 0x0 0x0x0 0x0x0
0xbfffe85c: 0x0 0x0x0 0x0x0
0xbfffe86c: 0x0 0x0x0 0x0x0
0xbfffe87c: 0x0 0x0x0 0x0x0
The stack content is the signal stack sigframe:
According to this structure, you can know:
1). Return address 0xb7fff400, which points to sigreturn in vdso
(Gdb) x/10i 0xb7fff400
0xb7fff400 <__ kernel_sigreturn>: pop % eax
0xb7fff401 <__ kernel_sigreturn + 1>: mov $0x77, % eax
0xb7fff406 <__ kernel_sigreturn + 6>: int $0x80
This address varies with the kernel. My kernel version is 2.6.38.
2) After the signal processing program is completed, it will return to the eip = 0x8048336 address for further execution.
2.4 After the sig_int function is executed, it enters _ kernel_sigreturn and returns to code 0x8048336. Everything returns to normal.
(Gdb) x/5i $ pc
=> 0x8048336 <main + 58>: jmp 0x8048336 <main + 58>
(Gdb) I r esp
Esp 0xbfffed40 0xbfffed40
What we can see at the user layer is only so much information above. There may be a point that we cannot understand: where does the content on the stack starting from 0xbfffe7ec in the process above come from? (Normally, the stack esp should always point to the esp value 0xbfffed40 displayed in process d)
Now let's take a look at the changes in the kernel stack under these phenomena.
3. What happened in the kernel stack
3.1 Signal Generation
In 2.2, after kill-INT 4639 is executed, the pid is 4639 (that is, the program we run. /sigint) will receive a signal, but the signal is actually implemented in the kernel. Each process (here only describes the process, the thread is similar, the thread has a tid) has a pid, which corresponds to a structure task_struct, in task_struct, there is a variable struct sigpending pending. When the process receives a signal, it does not immediately respond, just let the kernel record this signal in this variable (it contains a linked list structure ). Of course, this has little to do with the kernel stack.
3.2 Detection Signal
If only the signal is recorded, but there is no response, what is the purpose. Under what circumstances will a process detect the existence of signals? In the <scenario analysis>, "in the interrupt mechanism, the processor hardware checks whether there are interrupt requests when each command ends. The signal mechanism is purely software, and of course it cannot rely on hardware to detect the arrival of signals. At the same time, it is unrealistic or even impossible to detect each command at the end. Therefore, the signal detection mechanism is: every time the call from the system, interrupt processing or Exception Processing is returned to the user space on the eve; in addition, when a process is awakened from sleep (must be called by the system), if a signal is sent to wait, it must be returned from the system call in advance. All in all, whether it is a normal return or an early return, we always need to detect the existence of signals and respond on the eve of the return to the user space ."
Therefore, the response time to the received signal is on the eve of the kernel's return to the user space. Which of the following situations will allow the program to enter the kernel? The answer is interruptions, exceptions, and system calls. A Brief Introduction to the kernel stack changes when they occur.
// ----- Interrupt, exception, system call: Start
1) when the user space is interrupted, the CPU automatically saves the SS of the user stack, the ESP, EFLAGS, CS, EIP, and interrupt number-256 of the user stack in the kernel space.
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP | interrupt number-256
After entering the kernel, A SAVE_ALL will be executed, so the content on the kernel stack is:
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP | interrupt number-256 | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
Okay, when everything is done, the kernel jmp goes to RESTORE_ALL (it is a macro, for example, in the x86_32 architecture, the/usr/src/kernel/arch/286/kernel/entry_32.S file contains the macro definition)
The RESTORE's work can be seen from its code:
First, pop the ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX on the stack to the corresponding register.
Then, use esp + 4 to pop the "interrupt number-256 ".
The content on the kernel stack is:
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP
Finally, execute the iret command. At this time, the CPU extracts SS, ESP, ELFGAS, CS, and EIP from the kernel stack, and then runs.
2) When an exception occurs in the user space, the content automatically stored in the kernel stack by the CPU is as follows:
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP | error code error_code
(Note: The CPU only knows whether to push the error code into the stack when an exception occurs (Why ?), However, when the iret command is returned for exception handling, the CPU has no idea why the exception occurred. Therefore, this item will not be automatically skipped, the stack needs to be adjusted based on the corresponding exception program, so that the return address is at the top of the stack when the CPU starts to execute the iret command)
After entering the kernel, The SAVE_ALL operation is not performed, but the corresponding exception handling function is entered (this function is packaged and the real processing function is later) (In this function, the address of the real handler function will be pushed to the stack), and then jmp will go to the program entry error_code shared by various exception handling methods, it stores the corresponding registers (not ES) Like SAVE_ALL, And the content in the kernel space is:
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP | error code error_code | corresponding exception handling function entry | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
(Note: If there is no error code, this value is 0)
The last end is similar to the interrupt (RESTORE_ALL ).
3) when a system call occurs, the content of the CPU automatically stored on the kernel stack is:
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP
To be consistent with the interrupt and abnormal stack, % eax is pushed first after ENTRY (system_call) and SAVE_ALL is performed. The content on the kernel stack is
| User stack SS | user stack ESP | EFLAGS | user space CS | EIP | EAX | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
The last end is similar to the interrupt (RESTORE_ALL ).
// ----- Interrupt, exception, system call: End
Interruptions, exceptions, and system calls are omitted: the timing of the detection signal is next to RESTORE_ALL.
3.3 respond to detected Signals
If there is a signal to be processed, we need to start some preparation work. At this time, the content in the kernel is (when entering the kernel site)
| SS1 of user stack | ESP1 of user stack | EFLAGS1 | CS1 of user space | EIP1 |? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
(Note :? There are three options: interrupt number-256/error code error_code)
Assume that the signal processing program corresponding to the signal to be processed is set by the user, that is, the signal processing program sig_int corresponding to SIGINT in this article.
The current task is to let the cpu execute the signal processing program sig_int, but make preparations before execution:
3.3.1 setup_frame
Set the signal stack (struct sigframe) in the user space (assuming that the esp value after the stack is set is sigframe_esp, and the value in this article is 0xbfffe7ec), that is, the stack content seen in 2.3.
Note: struct sigframe contains at least the following content:
User stack SS1, user stack ESP1, EFLAGS1, user space CS1, EIP1, ES1, DS1, EAX1, EBP1, EDI1, ESI1, EDX1, ECX1, EBX1
3.3.2 set the value of the eip to be run to the address of the signal processing function sig_int (0x80482e8), and set the value of user ESP to sigframe_esp (0xbfffe7ec ), this is achieved by modifying the values of EIP and ESP in the kernel stack, because EIP and ESP will be obtained from the kernel stack when the iret is called from the system.
At this time, the kernel of the kernel stack is:
| SS1 of the user stack | 0xbfffe7ec | EFLAGS1 | CS1 of the user space | 0x80482e8 |? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
Finally, perform RESTORE_ALL. The content on the kernel stack is:
| SS1 of the user stack | 0xbfffe7ec | EFLAGS1 | CS1 of the user space | 0x80482e8
After the iret is executed in RESTORE_ALL, the register content is: EIP is 0x80482e8 (sig_int), and esp is 0xbfffe7ec. So the user space reaches Step 2.3.
3.4 after the signal processing program is completed
2.3-> 2.4, entering the sig_return system call, in sig_return, the kernel stack content is (each name is followed by a 2 to distinguish it from the previous 1)
| SS2 of the user stack | ESP2 of the user stack | EFLAGS2 | CS2 of the user space | EIP2 |? | ES2 | DS2 | EAX2 | EBP2 | EDI2 | ei2 | EDX2 | ECX2 | EBX2
The main task of sig_return is to modify the content in the kernel stack based on the sigframe value in the user stack, so that the kernel stack is changed:
| SS1 of user stack | ESP1 of user stack | EFLAGS1 | CS1 of user space | EIP1 |? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
So far, the content in the kernel stack is the same as before signal processing. After RESTORE_ALL, the content in the user stack is the same as before (mainly refers to the same ESP value ).
"Kill-INT 4639" is just an episode. The program runs from the original location.