kill() returns success, but the handler hasn't executed yet; several child processes exit simultaneously, but the parent only receives one SIGCHLD. Understanding these phenomena requires separating event generation, pending state, and actual delivery.
ptrace adds another layer of stop protocols: the tracer can observe the tracee at specific points, but must identify the reason for the stop to resume it correctly.
Phenomenon you encounter
Read section
No response after sending a signal, or only one thread receives it
Section 1: Pending and Thread Selection
Number of sends differs from number of handlers executed
Section 2: Merging, Queuing, and Experiments
Handler hangs, or wait timeouts keep increasing
Sections 3–4: Asynchronous Entry and EINTR
Target exits abnormally after debugger resumes it
Sections 5–6: Stop Classification and Access Boundaries
1. Generation, Pending, and Delivery are Three Different Stages
Successful return from the send interface usually means the signal request passed checks and was accepted by the kernel; it is not confirmation that the receiver has executed the handler. The receiving thread might be blocking that signal, not yet scheduled, or in a kernel path where it cannot respond temporarily. If your application needs to confirm that an operation is complete, you should design an explicit acknowledgment instead of treating the return value of kill() as a completion notification.
First, determine who owns the state:
State
Scope
Meaning
Disposition: default, ignore, handler
Usually shared within the thread group
How to handle after receipt
Signal mask
Per-thread independent
Whether this thread currently receives signals
Thread-directed pending signals
Specified thread
Wait until the target thread can receive
Process-directed pending signals
Can be saved in group-shared state
Select a receiver from suitable threads
kill(pid, sig) usually sends to the process; pthread_kill() specifies a thread. When the target thread blocks the signal, a thread-directed signal cannot be redirected to someone else.
When reading the diagram above, first look at A's mask, then at B and C's eligibility to receive: A blocks SIGUSR1, so B or C can be selected, but they won't each execute once. If all three threads block it, the signal remains pending until there is a suitable delivery opportunity.
Change the send target and masks, then try delivery on threads. Shared and private pending are shown separately; manually selecting threads is for comparing possible receivers and does not guarantee the kernel's selection order.
Preparing the visual
Sent to the process, or to a specific thread?
Process-directed signals can be received by one suitable thread; thread-directed signals can only be left for the specified thread. Disposition is usually shared, while masks and private pending have their own ownership.
{"id":"signal-routing","title":"Sent to the process, or to a specific thread?","summary":"Process-directed signals can be received by one suitable thread; thread-directed signals can only be left for the specified thread. Disposition is usually shared, while masks and private pending have their own ownership.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"target\"><button data-target=\"process\" data-i18n=\"process\"></button><button data-target=\"thread\" data-i18n=\"threadA\"></button></div><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button></div><div class=\"pending-group\"><span data-i18n=\"groupPending\"></span><strong id=\"group-token\">—</strong></div><div class=\"routes\" aria-hidden=\"true\"><i id=\"route-A\"></i><i id=\"route-B\"></i><i id=\"route-C\"></i></div><div class=\"threads\"><section class=\"card\" id=\"thread-A\"><header>A</header><label class=\"check\"><input type=\"checkbox\" data-block=\"A\" checked><span data-i18n=\"blocked\"></span></label><div class=\"private\"><small data-i18n=\"privatePending\"></small><strong id=\"private-A\">—</strong></div><button data-deliver=\"A\" data-i18n=\"deliver\"></button><div class=\"handled\"><small data-i18n=\"handlerCount\"></small><strong id=\"handled-A\">0</strong></div></section><section class=\"card\" id=\"thread-B\"><header>B</header><label class=\"check\"><input type=\"checkbox\" data-block=\"B\" ><span data-i18n=\"blocked\"></span></label><div class=\"private\"><small data-i18n=\"privatePending\"></small><strong id=\"private-B\">—</strong></div><button data-deliver=\"B\" data-i18n=\"deliver\"></button><div class=\"handled\"><small data-i18n=\"handlerCount\"></small><strong id=\"handled-B\">0</strong></div></section><section class=\"card\" id=\"thread-C\"><header>C</header><label class=\"check\"><input type=\"checkbox\" data-block=\"C\" ><span data-i18n=\"blocked\"></span></label><div class=\"private\"><small data-i18n=\"privatePending\"></small><strong id=\"private-C\">—</strong></div><button data-deliver=\"C\" data-i18n=\"deliver\"></button><div class=\"handled\"><small data-i18n=\"handlerCount\"></small><strong id=\"handled-C\">0</strong></div></section></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"initial\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"queuedGroup\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"queuedThread\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"blockedNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"deliveredNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"emptyNote\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.pending-group{border:1px solid var(--accent);background:var(--accent-soft);border-radius:9px;padding:14px;margin-top:18px;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:13px}.pending-group strong{font:600 16px var(--viz-font-ui)}.pending-group button{font-size:12px}.routes{display:grid;grid-template-columns:repeat(3,1fr);height:35px}.routes i{width:1px;justify-self:center;border-left:1px solid var(--rule)}.routes i.available{border-color:var(--accent);border-width:2px}.threads{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.threads .card{padding:12px 9px;text-align:center}.threads .check{font-size:12px;justify-content:center;min-height:3.4em;flex-wrap:wrap;gap:4px}.private{min-height:7em;border:1px dashed var(--rule);border-radius:6px;padding:8px 3px;margin:12px 0;font-size:11px}.private strong{display:block;font:500 12px var(--viz-font-ui);margin-top:8px}.private.pending{background:var(--second-soft);border-color:var(--second)}.handled{margin-top:12px}.handled strong{display:block;font-size:22px;margin-top:5px}.handled small{min-height:3.4em}.threads button{font-size:12px;padding-inline:3px;min-height:4.2em}.threads .card.delivered{border-color:var(--accent);background:var(--accent-soft)}@media(max-width:480px){.threads{gap:6px}.threads .card{padding:10px 5px}.private{min-height:8.5em}}","js":"const $=s=>document.querySelector(s);let target='process',group=false,local={A:false,B:false,C:false},handled={A:0,B:0,C:0},message='initial',last='';\nfunction draw(){$('#group-token').textContent=group?'SIGUSR1':'—';for(const k of 'ABC'){const blocked=document.querySelector('[data-block=\"'+k+'\"]').checked;$('#private-'+k).textContent=local[k]?'SIGUSR1':'—';$('#private-'+k).parentElement.classList.toggle('pending',local[k]);$('#handled-'+k).textContent=handled[k];$('#route-'+k).classList.toggle('available',group&&!blocked);$('#thread-'+k).classList.toggle('delivered',last===k);}document.querySelectorAll('[data-target]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.target===target)));$('#explanation').textContent=viz.t(message);}\ndocument.querySelectorAll('[data-target]').forEach(e=>e.addEventListener('click',()=>{target=e.dataset.target;draw();}));document.querySelectorAll('[data-block]').forEach(e=>e.addEventListener('change',()=>{last='';message='initial';draw();}));$('#send').addEventListener('click',()=>{if(target==='process'){group=true;message='queuedGroup';}else{local.A=true;message='queuedThread';}last='';draw();});document.querySelectorAll('[data-deliver]').forEach(e=>e.addEventListener('click',()=>{const k=e.dataset.deliver;if(document.querySelector('[data-block=\"'+k+'\"]').checked)message='blockedNote';else if(local[k]||group){if(local[k])local[k]=false;else group=false;handled[k]++;last=k;message='deliveredNote';}else{last='';message='emptyNote';}draw();}));$('#reset').addEventListener('click',()=>{group=false;local={A:false,B:false,C:false};handled={A:0,B:0,C:0};message='initial';last='';draw();});draw();\n","audio":false,"strings":{"blocked":"Block SIGUSR1","blockedNote":"This thread blocks SIGUSR1 and cannot deliver here. Pending remains.","deliver":"Attempt Delivery on This Thread","deliveredNote":"One pending has been taken; the handler executes only on the selected thread.","emptyNote":"This thread has no signals to claim; private pending cannot be transferred across threads.","eyebrow":"01 · Ownership of Signals","groupPending":"Process Shared Pending","handlerCount":"Handler Execution Count","heading":"Sent to the process, or to a specific thread?","initial":"A blocks this signal by default. Process signals can be received by B or C; signals specified for A can only be left for A.","intro":"First choose the send target, then change each thread's mask. Trigger a delivery opportunity on a thread to see which pending is taken.","note":"Each thread has installed the same handler. Clicking a thread represents one possible delivery opportunity and does not guarantee the kernel selects threads in click order. The diagram separates unblocking and delivery; in real systems, delivery may occur immediately after unblocking. Repeated standard signals merge; when both private and shared pending exist, this diagram prioritizes private items.","privatePending":"Thread Private Pending","process":"Entire Process","queuedGroup":"Signal enters process shared pending. It will only be delivered to one suitable thread, not broadcast to every thread.","queuedThread":"Signal enters A's private pending. Even if B and C are not blocked, they cannot claim it for A.","reset":"Clear signals and counts","scope":"Model Scope and Conventions","send":"Send SIGUSR1","target":"Send Target","threadA":"Thread A Only"}}
To handle signals centrally, you should block the target set before creating worker threads, so new threads inherit the mask, and a dedicated thread consumes them synchronously. This way, the reception location is arranged by the program, without relying on which thread happens to be selected.
SIGKILL and SIGSTOP cannot have handlers installed or be ignored; attempting to modify their disposition with sigaction() will fail; adding them to the mask set also does not truly block them. They are still constrained by execution state: SIGKILL does not mean the CPU arbitrarily discards kernel stacks holding locks; the task may have to wait until it can reach a path that handles fatal signals. Wait designs like TASK_KILLABLE determine whether the corresponding wait can be interrupted by fatal signals. signal(7)
2. Why Standard Signals Cannot Be Used as Event Counters
If you block SIGUSR1 and send it five times consecutively, do not expect the handler to execute five times when you unblock it: when a standard signal of the same number is already pending, subsequent generation usually merges. It retains the state that "this type of event needs attention."
SIGCHLD: One Notification, Recycling May Take Multiple Steps
The correct way to consume SIGCHLD can be derived from this: after one notification arrives, you should repeatedly call the appropriate non-blocking wait to retrieve the status of all currently recyclable child processes until there are no more results. If you only recycle one, the others may have already exited, without independent subsequent SIGCHLDs to remind you. Even if signals merge, the exit status is still saved by the kernel's child process wait mechanism; the notification and the fact record belong to different objects.
Realtime Signals: Ordered Queuing Still Requires Handling Resource Limits
Realtime signals can carry values via sigqueue(), and multiple instances of the same signal are delivered in the order they were sent. The diagram retains the three values 10, 11, and 12, but the queue capacity is limited, and sending may fail with EAGAIN.
Different realtime signal numbers also have priority order; you cannot merge all numbers into a single global FIFO. When using SIGRTMIN + n, you should check SIGRTMAX; glibc may reserve realtime signals for the thread library, and SIGRTMIN cannot be hardcoded as 34. signal(7)
After sending consecutively, retrieve the two types of signals separately. The diagram deliberately uses a small capacity to expose send failures; it is not Linux's fixed queue limit.
Preparing the visual
Sent three times, can you retrieve three times?
Standard signals of the same number merge into one pending state; realtime signals of the same number queue separately and are consumed in order. Queue resources are limited; failed send values do not enter the queue.
{"id":"signal-queues","title":"Sent three times, can you retrieve three times?","summary":"Standard signals of the same number merge into one pending state; realtime signals of the same number queue separately and are consumed in order. Queue resources are limited; failed send values do not enter the queue.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"send-one\" data-i18n=\"sendOne\"></button><button id=\"send-three\" data-i18n=\"sendThree\"></button></div><div class=\"queue-panel\"><header><strong>SIGUSR1</strong><span data-i18n=\"standard\"></span></header><div class=\"standard-slot\" id=\"standard-token\">—</div><div class=\"queue-result\" id=\"standard-count\"></div><button id=\"take-standard\" data-i18n=\"take\"></button></div><div class=\"queue-panel\"><header><strong>SIGRTMIN</strong><span data-i18n=\"realtime\"></span></header><div class=\"slots\"><div id=\"slot-0\">—</div><div id=\"slot-1\">—</div><div id=\"slot-2\">—</div><div id=\"slot-3\">—</div></div><div class=\"queue-result\" id=\"rt-count\"></div><button id=\"take-rt\" data-i18n=\"take\"></button></div><div class=\"metrics\"><div><span data-i18n=\"lastStandard\"></span><strong id=\"last-standard\">—</strong></div><div><span data-i18n=\"lastRealtime\"></span><strong id=\"last-rt\">—</strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"initial\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"sent\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"full\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"taken\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.queue-panel{border:1px solid var(--rule);border-radius:9px;padding:14px;margin-top:16px}.queue-panel header{display:flex;flex-wrap:wrap;justify-content:space-between;font-size:13px;gap:8px}.queue-panel header>span{color:var(--muted)}.standard-slot{background:var(--surface);border:1px dashed var(--rule);padding:12px;text-align:center;font-size:18px;border-radius:6px;margin-top:14px}.standard-slot.pending{background:var(--accent-soft);border-color:var(--accent)}.slots{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:14px}.slots div{border:1px dashed var(--rule);border-radius:6px;padding:12px 5px;text-align:center;font:500 18px var(--viz-font-ui)}.slots .pending{background:var(--second-soft);border-color:var(--second)}.queue-result{font-size:12px;margin:12px 0;min-height:3.4em;color:var(--muted)}.queue-panel button{width:100%;min-height:42px;font-size:13px}","js":"const $=s=>document.querySelector(s);let pending=false,queue=[],sent=0,accepted=0,rejected=0,takenStandard=0,takenRT=0,lastStandard='—',lastRT='—',message='initial';\nfunction draw(){$('#standard-token').textContent=pending?'SIGUSR1':'—';$('#standard-token').classList.toggle('pending',pending);$('#standard-count').textContent=viz.t('pendingCount')+' '+Number(pending)+' · '+viz.t('consumed')+' '+takenStandard;for(let i=0;i<4;i++){$('#slot-'+i).textContent=queue[i]??'—';$('#slot-'+i).classList.toggle('pending',i<queue.length);}$('#rt-count').textContent=viz.t('accepted')+' '+accepted+' · '+viz.t('rejected')+' '+rejected+' · '+viz.t('pendingCount')+' '+queue.length;$('#last-standard').textContent=lastStandard;$('#last-rt').textContent=lastRT;$('#take-standard').disabled=!pending;$('#take-rt').disabled=!queue.length;$('#explanation').textContent=viz.t(message);}\nfunction send(n){let full=false;for(let i=0;i<n;i++){const value=10+sent;sent++;pending=true;if(queue.length<4){queue.push(value);accepted++;}else{rejected++;full=true;}}message=full?'full':'sent';draw();}\n$('#send-one').addEventListener('click',()=>send(1));$('#send-three').addEventListener('click',()=>send(3));$('#take-standard').addEventListener('click',()=>{pending=false;takenStandard++;lastStandard='SIGUSR1';message='taken';draw();});$('#take-rt').addEventListener('click',()=>{lastRT=queue.shift();takenRT++;message='taken';draw();});$('#reset').addEventListener('click',()=>{pending=false;queue=[];sent=accepted=rejected=takenStandard=takenRT=0;lastStandard=lastRT='—';message='initial';draw();});draw();\n","audio":false,"strings":{"accepted":"Realtime Signal Accepted","consumed":"Retrieved","eyebrow":"02 · Pending and Queuing","full":"Queue is full, this realtime send failed. Standard signal still has only one pending bit; failed values are not queued.","heading":"Sent three times, can you retrieve three times?","initial":"Send consecutively first, then retrieve separately. Compare one pending bit with multiple ordered queue items.","intro":"Both signal types remain blocked. Each send generates both a standard signal and a realtime signal with a value, then retrieve them separately.","lastRealtime":"Last Retrieved Realtime Value","lastStandard":"Last Retrieved Standard Signal","note":"Consume blocked signals using synchronous interfaces like sigtimedwait. Send standard signals with kill, do not fabricate a payload for each send. Realtime queue capacity of 4 is for illustration only; Linux limits are controlled by resources, and sigqueue can return EAGAIN. Here we only compare the same realtime signal.","pendingCount":"Pending to Retrieve","realtime":"Same Realtime Signal · FIFO","rejected":"Realtime Signal Send Failed","reset":"Clear Queue","scope":"Model Scope and Conventions","sendOne":"Send Each Once","sendThree":"Send Each Consecutively Three Times","sent":"Standard signal leaves only one pending bit; realtime signals of the same number save each accepted value in order.","standard":"Standard Signal · SIGUSR1","take":"Retrieve One","taken":"Retrieved results remain below for comparison. Standard signal is cleared after one retrieval; realtime signals are taken item by item from the same-number FIFO."}}
Experiment: One Pending and Three Queued Values
Below, we block signals within a single-threaded process to verify the merging of five standard signals and the ordered retrieval of three realtime signals of the same number. It does not install asynchronous handlers, nor does it send signals to other processes. Save as pending.c, compile with cc -std=c11 -O2 -Wall -Wextra -Werror pending.c -o pending, and do not define NDEBUG.
#define_POSIX_C_SOURCE200809L#include<assert.h>#include<errno.h>#include<signal.h>#include<stdio.h>#include<time.h>#include<unistd.h>intmain(void){constint rt = SIGRTMIN;assert(rt <= SIGRTMAX);
sigset_t all, old, standard, realtime;sigemptyset(&all);sigaddset(&all, SIGUSR1);sigaddset(&all, rt);assert(sigprocmask(SIG_BLOCK,&all,&old)==0);struct sigaction sa ={.sa_handler= SIG_DFL };sigemptyset(&sa.sa_mask);assert(sigaction(SIGUSR1,&sa,NULL)==0);assert(sigaction(rt,&sa,NULL)==0);for(int i =0; i <5;++i)assert(kill(getpid(), SIGUSR1)==0);for(int i =10; i <13;++i){union sigval value ={.sival_int= i };assert(sigqueue(getpid(), rt, value)==0);}sigemptyset(&standard);sigaddset(&standard, SIGUSR1);sigemptyset(&realtime);sigaddset(&realtime, rt);struct timespec zero ={0};
siginfo_t info;assert(sigtimedwait(&standard,&info,&zero)== SIGUSR1);
errno =0;assert(sigtimedwait(&standard,&info,&zero)==-1&& errno == EAGAIN);for(int expected =10; expected <13;++expected){assert(sigtimedwait(&realtime,&info,&zero)== rt);assert(info.si_code== SI_QUEUE);assert(info.si_value.sival_int== expected);}
errno =0;assert(sigtimedwait(&realtime,&info,&zero)==-1&& errno == EAGAIN);assert(sigprocmask(SIG_SETMASK,&old,NULL)==0);puts("standard: 1 pending; realtime payloads: 10, 11, 12");}
Zero timeout means only matching signals that are already pending at this moment are taken; if none, it returns EAGAIN. The program consumes all signals it sent before restoring the original mask. The experiment assumes no external senders and sufficient queue quota; production programs must handle sigqueue resource shortages and cannot rely on assertions for recovery strategies. sigtimedwait(2)
3. How Handlers Enter and How to Return to the Interrupted Position
For deliveries that require calling a user handler, the kernel prepares a signal frame at the appropriate user-mode return boundary: it saves the user registers, mask, and relevant context needed for restoration, selects the main user stack or a suitable alternate signal stack, and changes the user execution entry point to the handler. It runs in the receiving thread's user-mode context, not as a kernel interrupt handler, nor as a new thread temporarily created by the kernel.
After the handler returns, it enters rt_sigreturn via the applicable user-mode return trampoline. The kernel verifies and restores the corresponding state, continuing the interrupted execution. Frame layout and extended register details vary by architecture and ABI; you cannot arbitrarily interpret all architectures using a single assumed fixed-size C struct. Default termination, stop, or ignore actions also do not need to follow this "call user handler" path.
Select the execution position of the main program, then deliver a signal at this point. The TID remains unchanged; observe this entry and return from the PC, temporarily saved frame, and mask.
Preparing the visual
Whose execution thread does the handler borrow?
The handler executes in the receiving thread's user-mode context. The signal frame saves the interrupt context, rt_sigreturn restores PC and mask, and modifications to flags by the handler are retained.
{"id":"signal-handler-context","title":"Whose execution thread does the handler borrow?","summary":"The handler executes in the receiving thread's user-mode context. The signal frame saves the interrupt context, rt_sigreturn restores PC and mask, and modifications to flags by the handler are retained.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"thread-identity\"><strong>TID 5101</strong><span id=\"execution\"></span></div><div class=\"main-code\"><button id=\"line-0\" data-location=\"0\"><span>1</span><code>prepare();</code></button><button id=\"line-1\" data-location=\"1\"><span>2</span><code>compute();</code></button><button id=\"line-2\" data-location=\"2\"><span>3</span><code>use_result();</code></button><button id=\"line-3\" data-location=\"3\"><span>4</span><code>loop();</code></button></div><div class=\"context-values\"><span>flag = <b id=\"flag\">0</b></span></div><div class=\"signal-frame\" id=\"signal-frame\"><header data-i18n=\"frame\"></header><div><span data-i18n=\"savedPC\"></span><strong id=\"saved-pc\">—</strong></div><div><span data-i18n=\"savedMask\"></span><code>∅</code></div><code class=\"handler-code\">handler: flag = 1;</code><button id=\"return\" data-i18n=\"return\"></button></div><div class=\"mask\"><span data-i18n=\"currentMask\"></span><strong id=\"mask\">∅</strong></div><div class=\"actions\"><button id=\"interrupt\" data-i18n=\"interrupt\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"mainNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"handlerNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"returnedNote\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.thread-identity{display:flex;justify-content:space-between;gap:12px;padding:14px;border:1px solid var(--accent);border-radius:9px 9px 0 0;background:var(--accent-soft);font-size:13px}.thread-identity>strong{white-space:nowrap;flex-shrink:0}.thread-identity>span{min-width:0;min-height:3.2em}.main-code{border:1px solid var(--rule);border-top:0;padding:12px}.main-code>button{display:flex;justify-content:flex-start;width:100%;gap:18px;padding:7px;border-radius:5px;font-size:13px;border-color:transparent;min-height:40px}.main-code>button:disabled{opacity:1}.main-code span{color:var(--muted)}.main-code .current{background:var(--accent-soft);color:var(--accent)}.main-code .suspended{background:var(--surface);outline:1px dashed var(--muted)}.context-values{display:flex;gap:24px;flex-wrap:wrap;padding:14px 0;font-size:13px}.signal-frame{border:1px dashed var(--rule);border-radius:8px;padding:14px;opacity:.55;transition:opacity .25s,background .25s}.signal-frame.active{opacity:1;background:var(--second-soft);border-color:var(--second)}.signal-frame header{font-size:13px;font-weight:600;margin-bottom:10px}.signal-frame>div{display:flex;justify-content:space-between;gap:12px;margin:8px 0;font-size:12px}.handler-code{display:block;padding:10px 0;font-size:13px}.signal-frame button{width:100%;font-size:13px}.mask{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:14px;font-size:12px}.mask strong{font-family:var(--viz-font-ui)}","js":"const $=s=>document.querySelector(s);let pc=0,flag=0,active=false,saved=null,message='mainNote';\nfunction draw(){$('#execution').textContent=viz.t(active?'handler':'main');for(let i=0;i<4;i++){$('#line-'+i).classList.toggle('current',pc===i&&!active);$('#line-'+i).disabled=active;$('#line-'+i).setAttribute('aria-pressed',String(pc===i));$('#line-'+i).classList.toggle('suspended',pc===i&&active);}$('#flag').textContent=flag;$('#signal-frame').classList.toggle('active',active);$('#saved-pc').textContent=saved===null?'—':viz.t('line')+' '+(saved+1);$('#mask').textContent=active?'SIGUSR1':'∅';$('#interrupt').disabled=active;$('#return').disabled=!active;$('#explanation').textContent=viz.t(message);}\ndocument.querySelectorAll('[data-location]').forEach(e=>e.addEventListener('click',()=>{pc=+e.dataset.location;message='mainNote';draw();}));\n$('#interrupt').addEventListener('click',()=>{saved=pc;active=true;flag=1;message='handlerNote';draw();});$('#return').addEventListener('click',()=>{pc=saved;saved=null;active=false;message='returnedNote';draw();});$('#reset').addEventListener('click',()=>{pc=flag=0;saved=null;active=false;message='mainNote';draw();});draw();\n","audio":false,"strings":{"currentMask":"Current Signal Mask","eyebrow":"03 · Signal Context","frame":"Temporary User-mode Signal Frame","handler":"Handler Executing","handlerNote":"Main program pauses; the same TID is executing the handler. The original context remains in the signal frame; by default, the current signal is temporarily blocked.","heading":"Whose execution thread does the handler borrow?","interrupt":"Deliver SIGUSR1 Here","intro":"Directly click a code line to select the interrupt position, then deliver a signal. Observe the execution position, saved frame, and context after return for the same TID.","line":"Line","main":"Main Program Executing","mainNote":"Main program is executing. Delivery saves its current PC and mask, then lets the same thread enter the handler.","note":"The diagram selects the moment a signal is delivered at a user-mode code position, omitting architecture ABI, alternate signal stacks, and nesting. The handler sets a volatile sig_atomic_t flag to 1; it is not a new thread. The box only sketches the key fields; the temporary frame is no longer used after return. Clicking a code line only selects the interrupt position in the snapshot and does not simulate the internal execution of the four illustrative functions.","reset":"Reset","return":"Handler Return · rt_sigreturn","returnedNote":"rt_sigreturn restores the original PC and mask. The main program continues from the interrupt point; flags set by the handler are retained.","savedMask":"Saved Mask","savedPC":"Saved PC","scope":"Model Scope and Conventions"}}
Why Handlers Cannot Print Logs Normally
Suppose the original thread is executing stdio, and its internal state has not finished updating. If the signal handler inserts execution at this time, and the handler calls printf, it may re-enter the same unfinished state; if it tries to acquire a lock already held by the original thread, it might wait for itself to unlock.
This is the source of asynchronous signal safety restrictions. Handlers should only perform allowed operations, such as setting a volatile sig_atomic_t flag, or writing to a notification channel with non-blocking/capacity policies, and then hand it over to the normal execution flow. signal-safety(7)
Moving Complex Processing Back to the Normal Thread
sig_atomic_t is suitable for signal handling communication within a specified range, but it does not automatically provide the release/acquire protocol required for multi-threaded data publication. If the requirement is indeed to centrally handle asynchronous requests like exit or reload, you can use sigwaitinfo() or Linux signalfd(), arranging the processing in the normal thread execution flow; in multi-threaded environments, ensure relevant threads consistently block these signals to avoid delivery by other threads first. SIGSEGV generated by illegal memory access or other synchronization exceptions cannot mechanically apply this centralized consumption mode to recover the program.
4. EINTR, Restart, and the Race Condition of "Check Then Sleep"
After a wait is interrupted by a signal, first determine what the interface actually returned:
Result
Basis for Subsequent Handling
Returns EINTR
Decide to retry, cancel, or return to upper layer based on interface semantics
read/write returns positive short count
Record completed bytes first, then handle the remainder
Interface automatically restarts according to rules
The call may continue waiting; the scope of SA_RESTART still depends on the interface
Short returns may have already completed part of the work and cannot be uniformly resubmitted as "did nothing."
Timeout Retry: Retain the Original Deadline
SA_RESTART is not a global "never EINTR" switch. For example, nanosleep and various wait interfaces have their own interruption rules. Loops with timeouts must retain the original deadline: if you wait the full 5 seconds every time you are interrupted, frequent signals may cause the total wait to far exceed 5 seconds. Use a monotonic clock to calculate the remaining budget, or use interfaces that support absolute deadlines, to maintain end-to-end time limits.
Wait Race Condition: Flag is True, But pause() Just Started
Consider this sequence: the main program checks the flag and finds it false; the signal handler then sets it true; the main program finally calls pause(). The notification has passed, but the main program will wait for the next signal.
Usually, you should first block the target signal and check the flag, then use sigsuspend() to atomically replace the mask and wait; re-check the condition after returning. It protects the same window as Condition Wait: from checking the condition to entering sleep. signal(7)
5. ptrace: Identify the Stop Reason First, Then Decide How to Resume
The tracee in ptrace is a thread in Linux. Establishing a tracing relationship with a TID does not mean other threads stop simultaneously; the debugger must also manage thread discovery, creation events, and the stop state of each thread to construct a consistent view of the entire process.
PTRACE_ATTACH establishes the relationship and sends a stop request, but the return of the call does not mean the target has entered a checkable stop; the tracer should wait for the corresponding state. PTRACE_SEIZE does not stop the target itself; you can request a stop later with PTRACE_INTERRUPT. The tracing loop cannot simply be "wait returns, then read registers," because wait may also report final exit or other events.
You can establish the most basic classification using the table below, then handle options, thread groups, and kernel version differences:
Observed Status
Meaning and Subsequent Action
Normal Exit or Signal Termination
Consume final status, clean up tracing records; no longer send resume requests
Normal Signal Delivery Stop
Can check siginfo, choose to suppress or inject the signal on resume
Syscall Stop
Often identified by SIGTRAP | 0x80 after enabling TRACESYSGOOD; this is a classification marker, not a signal to actually forward
ptrace Event Stop
Decode events in wait status, such as clone, exec, exit; event stops are still different from final exits
Job Control Group Stop
Needs to be distinguished from signal delivery stops; in SEIZE mode, can be handled with protocols like LISTEN
Below, compare resume decisions using independent stop snapshots. Only real SIGUSR1 delivery stops provide the option to forward; the target thread's handler result will be retained after resume.
Preparing the visual
Seeing SIGTRAP, Should You Forward SIGTRAP?
Before ptrace resume, you must distinguish between real signal delivery, debug events, and final exit. Real signals can be forwarded or suppressed on resume; debug markers do not equal standard signals that should be forwarded.
{"id":"ptrace-stop-protocol","title":"Seeing SIGTRAP, Should You Forward SIGTRAP?","summary":"Before ptrace resume, you must distinguish between real signal delivery, debug events, and final exit. Real signals can be forwarded or suppressed on resume; debug markers do not equal standard signals that should be forwarded.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"case-control\"><label for=\"stop-kind\" data-i18n=\"stopKind\"></label><select id=\"stop-kind\"><option value=\"entry\" data-i18n=\"entry\"></option><option value=\"exit\" data-i18n=\"sysExit\"></option><option value=\"signal\" data-i18n=\"signal\"></option><option value=\"exec\" data-i18n=\"exec\"></option><option value=\"final\" data-i18n=\"final\"></option></select></div><div class=\"stop-token\"><span data-i18n=\"reported\"></span><code id=\"report\"></code></div><div class=\"two\"><section class=\"card tracer\"><header data-i18n=\"tracer\"></header><div class=\"classification\" id=\"classification\"></div><label class=\"check\"><input type=\"checkbox\" id=\"inject\"><span data-i18n=\"inject\"></span></label><button id=\"resume\" data-i18n=\"resume\"></button><code id=\"command\"></code></section><section class=\"card tracee\"><header>TID 5101</header><div id=\"tracee-state\" class=\"tracee-state\"></div><small class=\"signal-label\" data-i18n=\"deliveredLabel\"></small><div class=\"delivered-signal\" id=\"delivered-signal\">—</div><div class=\"handler-flag\"><span>handler_flag</span><strong id=\"handler-flag\">0</strong></div></section></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"eventNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"signalNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"finalNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"forwardedNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"suppressedNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"continuedNote\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.case-control{display:grid;gap:8px;font-size:13px}.case-control select{width:100%;min-width:0;background:var(--paper);color:var(--ink);border:1px solid var(--rule);border-radius:7px;padding:10px;font:inherit}.stop-token{margin-top:16px;padding:14px;background:var(--surface);border:1px solid var(--rule);border-radius:8px}.stop-token span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.stop-token code{font-size:12px;overflow-wrap:anywhere;display:block;min-height:3.4em}.classification{font-size:13px;min-height:5.1em}.tracer .check{min-height:6em;font-size:12px}.tracer>code{display:block;font-size:11px;margin-top:12px;min-height:6em;overflow-wrap:anywhere}.tracee-state{font-size:14px;min-height:5.1em}.signal-label{min-height:4.2em}.delivered-signal{padding:14px 5px;border:1px dashed var(--rule);border-radius:6px;font:500 13px var(--viz-font-ui);text-align:center;min-height:4em}.delivered-signal.received{background:var(--second-soft);border-color:var(--second)}.handler-flag{margin-top:14px;font-size:12px}.handler-flag strong{display:block;font-size:24px}.tracee.running{background:var(--accent-soft);border-color:var(--accent)}","js":"const $=s=>document.querySelector(s);let resumed=false,flag=0,message='eventNote';\nfunction draw(){const kind=$('#stop-kind').value,signal=kind==='signal',final=kind==='final';$('#report').textContent={entry:'WSTOPSIG = SIGTRAP | 0x80 · ENTRY',exit:'WSTOPSIG = SIGTRAP | 0x80 · EXIT',signal:'WSTOPSIG = SIGUSR1 · signal-delivery-stop',exec:'WSTOPSIG = SIGTRAP · PTRACE_EVENT_EXEC',final:'WIFEXITED = true · exit code 0'}[kind];$('#classification').textContent=viz.t(final?'finalClass':signal?'signalClass':'eventClass');$('#inject').disabled=!signal||resumed;if(!signal)$('#inject').checked=false;$('#resume').disabled=final||resumed;$('#tracee-state').textContent=viz.t(final?'exited':resumed?'running':'stopped');$('#delivered-signal').textContent=flag?'SIGUSR1':'—';$('#delivered-signal').classList.toggle('received',!!flag);$('#handler-flag').textContent=flag;document.querySelector('.tracee').classList.toggle('running',resumed);$('#command').textContent=final?'—':'PTRACE_CONT(..., '+(signal&&$('#inject').checked?'SIGUSR1':'0')+')';$('#explanation').textContent=viz.t(message);}\nfunction reset(){resumed=false;flag=0;$('#inject').checked=false;const k=$('#stop-kind').value;message=k==='final'?'finalNote':k==='signal'?'signalNote':'eventNote';draw();}\n$('#stop-kind').addEventListener('change',reset);$('#inject').addEventListener('change',draw);$('#resume').addEventListener('click',()=>{resumed=true;if($('#stop-kind').value==='signal'){flag=Number($('#inject').checked);message=flag?'forwardedNote':'suppressedNote';}else message='continuedNote';draw();});$('#reset').addEventListener('click',reset);draw();\n","audio":false,"strings":{"continuedNote":"Resumed with signal parameter 0; did not inject the debug marker into the application. The target continues executing.","deliveredLabel":"Signal Actually Delivered to Target","entry":"Syscall Entry","eventClass":"Debug Protocol Event Stop","eventNote":"This is a debug protocol event. The SIGTRAP marker helps classify, but you cannot treat it as the application receiving a standard SIGTRAP.","exec":"exec Event Stop","exited":"Exited","eyebrow":"04 · ptrace Stop Protocol","final":"Finally Exited","finalClass":"Final Exit Report","finalNote":"wait has confirmed final exit; this is not a stop waiting for resume. You can no longer resume this thread.","forwardedNote":"SIGUSR1 was forwarded on resume. The target's handler executes, setting handler_flag to 1.","heading":"Seeing SIGTRAP, Should You Forward SIGTRAP?","inject":"Forward SIGUSR1 on Resume","intro":"Select a wait report, first see which type of stop it belongs to, then decide whether to deliver a real signal on resume. The right side retains the impact on the target thread.","note":"Each option is an independent stop snapshot, assuming corresponding ptrace options are enabled and the target has installed a SIGUSR1 handler. Resume is demonstrated using PTRACE_CONT; subsequent syscall-stops are not automatically requested. Real debuggers must also handle group-stops, exit events, and multi-threading; non-delivery stops are not suitable for inferring signal injection.","reported":"wait Reported","reset":"Reset Current Scenario","resume":"Resume This Thread","running":"Running","scope":"Model Scope and Conventions","signal":"SIGUSR1 Delivery Stop","signalClass":"Real Signal Delivery Stop","signalNote":"This is a SIGUSR1 signal-delivery-stop. On resume, you can forward the signal or suppress it with 0; the choice changes target behavior.","stopKind":"Received Stop Report","stopped":"Stopped","suppressedNote":"Resume parameter is 0; the original SIGUSR1 is suppressed, the target continues executing, and handler_flag remains 0.","sysExit":"Syscall Exit","tracer":"Debugger's Judgment"}}
Resume Decision: Syscall Stops and Signal Delivery Are Different
PTRACE_SYSCALL lets the target continue to the next applicable syscall stop. If you simply maintain a boolean value, alternating between "entry/exit" at each stop, you may misalign when encountering signals, exec, seccomp events, or other stops. When supported, you can use PTRACE_GET_SYSCALL_INFO to get entry, exit, or seccomp info; you still must identify the stop type first and cannot blindly read syscall numbers for all wait results.
A common mistake is injecting all SIGTRAPs back on resume. Single-step or syscall events requested by the debugger themselves do not all represent that the target should have received a SIGTRAP; incorrect injection may cause a normally functioning program to execute a default termination action. Conversely, suppressing all real business signals also changes target behavior. The tracer's resume decision is part of program semantics. ptrace(2)
6. Reading Memory and seccomp: Observable Does Not Mean Consistent or Safe
PTRACE_PEEKDATA may legally read the value -1. To judge failure, you should first clear errno, then combine the return value and errno, rather than assuming the address is illegal just because you see -1. Using /proc/PID/mem or process_vm_readv() does not bypass access control; credentials, dumpable, user namespaces, Yama, or other LSM policies all affect allowed operations.
Consistency: Stopping One Thread, Other Writers Are Still Active
Passing permissions does not mean you read a consistent snapshot. Suppose the tracer only stops thread A, while thread B is still modifying a linked list; reading head then next may skip two structural changes. Even if all normal threads are stopped, other processes sharing memory or devices may still change the data. Analysis tools should clarify whether they need best-effort sampling or a consistent snapshot provided by program cooperation.
Interface Boundaries: TRACE and USER_NOTIF
seccomp's SECCOMP_RET_TRACE can generate seccomp trace events under corresponding ptrace options; SECCOMP_RET_USER_NOTIF hands them over to user-mode supervisors via a dedicated listener fd, which is not a ptrace interface.
The two can appear in the same complex system, but notification collection, response, and task lifecycle handling each have their own protocols. seccomp filters
User-mode supervisors reading pointers in system call arguments and then letting the original system call continue face a typical TOCTOU: after the check, other threads may modify the memory pointed to by the pointer. Merely "checking the path string" is not enough to guarantee the target ultimately operates on the same object.
ptrace itself is not a trusted sandbox boundary; responsibilities for observation, proxy execution, object references, and access control should be designed separately, rather than directly deriving "can safely authorize any system call" from "can intercept system calls." seccomp user notification
7. Next-Level Questions to Ask When Locating Issues Quickly
Whether to loop and consume all recyclable statuses after SIGCHLD
Abnormal exit after debugger resumes
Stop classification, and whether debug events were mistakenly injected as SIGTRAP
These entry points connect "what happened" to specific states; then look for the location of state changes in the source code.
Reading the kernel implementation can start from the generation/delivery path in kernel/signal.c, the relationship and permission management in kernel/ptrace.c, and then to architecture signal frames and ptrace register operations. The internal signal set and user-space sigset_t layout are not the same ABI, and there is no need to copy a struct definition that is easy to become outdated with version updates to understand the mechanism.
Process and thread resource relationships return to Lifecycle, and CPU competition after signal wake-up returns to the Scheduler.