Line data Source code
1 : /**
2 : * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include "stacktrace_signal.h"
12 : #include <stdatomic.h>
13 : #include "adiag_list.h"
14 : #include "adiag_print.h"
15 : #include "adiag_utils.h"
16 : #include "trace_system_api.h"
17 : #include "stacktrace_monitor.h"
18 :
19 : #define TRACE_STACK_SIGNAL_ENV "ASCEND_COREDUMP_SIGNAL"
20 : #define TRACE_STACK_SIGNAL_ENV_LENGTH 256U
21 : #define TRACE_STACK_SIGNAL_NONE "none"
22 : #define TRACE_STACK_SIGNAL_ALL "all"
23 : #define TRACE_STACK_SIGNAL_SLEEP 100000U // 100ms
24 :
25 : // Dedicated alternate signal stack. A stack-overflow SIGSEGV exhausts the
26 : // thread stack, so the handler must run on a separate stack (sigaltstack +
27 : // SA_ONSTACK) to be able to capture the crash at all.
28 : //
29 : // Size basis: the crash handler here only does a short prologue (copy siginfo/
30 : // ucontext into the global args, pipe/clone the asc_dumper sub-process, waitpid)
31 : // — the heavy work (ptrace stack unwind, file write) runs in the asc_dumper
32 : // child process, NOT on this stack. 64KB therefore comfortably exceeds
33 : // MINSIGSTKSZ plus the handler's own frame needs. A fixed compile-time constant
34 : // is used because on newer glibc SIGSTKSZ is a runtime sysconf value and cannot
35 : // size a static array.
36 : //
37 : // Limitation (single shared stack): sigaltstack is per-thread, but a newly
38 : // created thread inherits the parent's alt-stack *pointer*, so every thread in
39 : // the host process points at this one buffer. Two threads crashing
40 : // simultaneously would run their handlers on the same memory and corrupt each
41 : // other. This is mitigated, not fully eliminated, by:
42 : // - g_dumperMgr.mutex serialising the dump path so only one handler does the
43 : // clone/waitpid heavy work at a time;
44 : // - the crash ucontext/siginfo being copied into the global g_dumperMgr.args
45 : // (not kept on the alt stack) before the child reads them;
46 : // - sa_mask blocking all managed fatal signals on the *handling* thread so it
47 : // is not re-interrupted mid-handler.
48 : // A true per-thread alt stack would need to intercept host thread creation,
49 : // which a passively-loaded library cannot do; tracked as a follow-up.
50 : #define TRACE_ALT_STACK_SIZE (64U * 1024U)
51 : STATIC char g_traceAltStack[TRACE_ALT_STACK_SIZE];
52 :
53 : typedef struct {
54 : TraceSignalHandle func;
55 : const void* data;
56 : } TraceSigFuncNode;
57 : struct TraceSigAction {
58 : bool registerFlag; // has registered to system or not
59 : int32_t signo; // signal number
60 : TraceSignalHandle callbackFunc;
61 : struct sigaction sigAct; // the new action for signo
62 : struct sigaction oldSigAct; // the previous action for signo
63 : };
64 :
65 : struct TraceSigMgr {
66 : uint64_t timeStamp; // timestamp of signal handle callback
67 : atomic_bool exitFlag; // exit is running or not
68 : atomic_bool handleFlag; // signal_handle is running or not
69 : int32_t handlePid; // signal_handle is running in which process
70 : struct TraceSigAction sigAct[REGISTER_SIGNAL_NUM];
71 : };
72 :
73 : STATIC struct TraceSigMgr g_sigMgr = {
74 : .timeStamp = 0,
75 : .exitFlag = false,
76 : .handleFlag = false,
77 : .sigAct = {
78 : {.registerFlag = false, .signo = SIGINT}, // 2
79 : {.registerFlag = false, .signo = SIGTERM}, // 15
80 : {.registerFlag = false, .signo = SIGQUIT}, // 3
81 : {.registerFlag = false, .signo = SIGILL}, // 4
82 : {.registerFlag = false, .signo = SIGTRAP}, // 5
83 : {.registerFlag = false, .signo = SIGABRT}, // 6
84 : {.registerFlag = false, .signo = SIGBUS}, // 7
85 : {.registerFlag = false, .signo = SIGFPE}, // 8
86 : {.registerFlag = false, .signo = SIGSEGV}, // 11
87 : {.registerFlag = false, .signo = SIGXCPU}, // 24
88 : {.registerFlag = false, .signo = SIGXFSZ}, // 25
89 : {.registerFlag = false, .signo = SIGSYS}, // 31
90 : {.registerFlag = false, .signo = SIG_ATRACE} // 35
91 : }};
92 :
93 : /**
94 : * @brief : set timestamp
95 : * @return : NA
96 : */
97 88 : STATIC void TraceSignalSetTime(void)
98 : {
99 88 : uint64_t signalTime = GetRealTime();
100 88 : if (g_sigMgr.timeStamp == 0) {
101 24 : g_sigMgr.timeStamp = signalTime;
102 : }
103 88 : }
104 :
105 : /**
106 : * @brief : check function is enabled or not
107 : * @return : true enable; false disable
108 : */
109 1013 : STATIC bool TraceSignalCheckEnv(void)
110 : {
111 1013 : char envSignal[TRACE_STACK_SIGNAL_ENV_LENGTH] = {0};
112 1013 : const char* env = NULL;
113 1013 : MM_SYS_GET_ENV(MM_ENV_ASCEND_COREDUMP_SIGNAL, (env));
114 1013 : TraStatus ret = TraceHandleEnvString(env, envSignal, TRACE_STACK_SIGNAL_ENV_LENGTH);
115 1013 : if (ret != TRACE_SUCCESS) {
116 1013 : return true;
117 : }
118 0 : if (strcmp(envSignal, TRACE_STACK_SIGNAL_NONE) == 0) {
119 0 : ADIAG_RUN_INF(
120 : "get env %s = %s, close the signal capture function", TRACE_STACK_SIGNAL_ENV, TRACE_STACK_SIGNAL_NONE);
121 0 : return false;
122 : } else {
123 0 : ADIAG_WAR("env %s is invalid, use default signal capture function.", TRACE_STACK_SIGNAL_ENV);
124 : }
125 0 : return true;
126 : }
127 :
128 : /**
129 : * @brief signal handler function to register
130 : * @param [in] signo: signo, set by system
131 : * @param [in] siginfo: signo info, set by system
132 : * @param [in] ucontext: no use
133 : * @return NA
134 : */
135 88 : STATIC void TraceSignalHandler(int32_t signo, siginfo_t* siginfo, void* ucontext)
136 : {
137 88 : STACKTRACE_LOG_RUN("receive signal(%d).", signo);
138 88 : TraceSignalSetTime();
139 88 : StacktraceMonitorStartUpdate();
140 88 : TraceSignalInfo info = {signo, siginfo, ucontext, g_sigMgr.timeStamp};
141 1162 : for (uint32_t i = 0; i < REGISTER_SIGNAL_NUM; i++) {
142 1097 : if ((signo != g_sigMgr.sigAct[i].signo) || (g_sigMgr.sigAct[i].registerFlag != true)) {
143 1009 : continue;
144 : }
145 88 : LOGR("start to callback signal %d", signo);
146 88 : if (g_sigMgr.sigAct[i].callbackFunc != NULL) {
147 88 : g_sigMgr.sigAct[i].callbackFunc(&info);
148 : }
149 83 : if (signo == SIG_ATRACE) {
150 18 : if (ScdSignalIsBinDump(info.signo, info.siginfo)) {
151 : // StackcoreLogSave is safe here: g_stackLogMgr is initialized in
152 : // TraceDumperInit() which runs before TraceSignalInit() registers
153 : // signal handlers. StackcoreLogSave() also has NULL check protection.
154 18 : StackcoreLogSave();
155 : }
156 18 : return;
157 : }
158 : // Save crash signals here (SIG_ATRACE bin dump is saved above).
159 : // StackcoreLogSave is safe: g_stackLogMgr is initialized before signal
160 : // handlers are registered, and StackcoreLogSave() has NULL check protection.
161 65 : StackcoreLogSave();
162 : // recover the signal handler
163 65 : if (sigaction(g_sigMgr.sigAct[i].signo, &g_sigMgr.sigAct[i].oldSigAct, NULL) < 0) {
164 0 : g_sigMgr.sigAct[i].registerFlag = false;
165 0 : return;
166 : }
167 65 : (void)TraceRaise(signo);
168 : }
169 : }
170 :
171 : /**
172 : * @brief add func to signal list
173 : * @param [in] signo: signo array
174 : * @param [in] size: size of array
175 : * @param [in] func: func pointer
176 : * @return TraStatus
177 : */
178 973 : TraStatus TraceSignalAddFunc(const int32_t* signo, uint32_t size, TraceSignalHandle func)
179 : {
180 13622 : for (uint32_t i = 0; i < size; i++) {
181 177086 : for (uint32_t j = 0; j < REGISTER_SIGNAL_NUM; j++) {
182 164437 : if (signo[i] != g_sigMgr.sigAct[j].signo) {
183 151788 : continue;
184 : }
185 :
186 12649 : g_sigMgr.sigAct[j].callbackFunc = func;
187 : }
188 : }
189 973 : return TRACE_SUCCESS;
190 : }
191 :
192 : /**
193 : * @brief register signal callback to system
194 : * @return TraStatus
195 : */
196 1013 : STATIC TraStatus TraceSignalRegister(void)
197 : {
198 13922 : for (uint32_t i = 0; i < REGISTER_SIGNAL_NUM; i++) {
199 12929 : if (sigaction(g_sigMgr.sigAct[i].signo, &g_sigMgr.sigAct[i].sigAct, &g_sigMgr.sigAct[i].oldSigAct) < 0) {
200 20 : ADIAG_ERR(
201 : "register signal handler for signal %d failed, info: %s", g_sigMgr.sigAct[i].signo,
202 : strerror(AdiagGetErrorCode()));
203 20 : return TRACE_FAILURE;
204 : }
205 12909 : ADIAG_INF("register signal handler for signal %d succeed.", g_sigMgr.sigAct[i].signo);
206 12909 : g_sigMgr.sigAct[i].registerFlag = true;
207 : }
208 993 : ADIAG_INF("trace signal init succeed.");
209 993 : return TRACE_SUCCESS;
210 : }
211 :
212 : /**
213 : * @brief recover old signal callback
214 : * @return TraStatus
215 : */
216 1069 : STATIC void TraceSignalUnregister(void)
217 : {
218 14966 : for (uint32_t i = 0; i < REGISTER_SIGNAL_NUM; i++) {
219 13897 : if (!g_sigMgr.sigAct[i].registerFlag) {
220 1326 : continue;
221 : }
222 12571 : if (sigaction(g_sigMgr.sigAct[i].signo, &g_sigMgr.sigAct[i].oldSigAct, NULL) < 0) {
223 260 : ADIAG_ERR(
224 : "recover signal handler for signal %d failed, info: %s", g_sigMgr.sigAct[i].signo,
225 : strerror(AdiagGetErrorCode()));
226 : }
227 12571 : g_sigMgr.sigAct[i].registerFlag = false;
228 : }
229 1069 : ADIAG_RUN_INF("unregister all signal handlers, can not capture signal.");
230 1069 : }
231 :
232 154 : bool TraceSignalCheckExit(void) { return atomic_load(&g_sigMgr.exitFlag); }
233 :
234 1360 : void TraceSignalSetHandleFlag(bool value)
235 : {
236 1360 : g_sigMgr.handlePid = getpid();
237 1360 : atomic_store(&g_sigMgr.handleFlag, value);
238 1360 : }
239 :
240 : /**
241 : * @brief install a dedicated alternate signal stack so the handler can
242 : * still run when the thread stack is exhausted (stack-overflow
243 : * SIGSEGV). Failure is not fatal: fall back to the normal stack.
244 : * Runs before any signal handler is registered, so strerror is
245 : * used here without reentrancy concerns.
246 : * @return NA
247 : */
248 1013 : STATIC void TraceSignalSetupAltStack(void)
249 : {
250 : stack_t altStack;
251 1013 : (void)memset_s(&altStack, sizeof(altStack), 0, sizeof(altStack));
252 1013 : altStack.ss_sp = g_traceAltStack;
253 1013 : altStack.ss_size = sizeof(g_traceAltStack);
254 1013 : altStack.ss_flags = 0;
255 1013 : if (sigaltstack(&altStack, NULL) != 0) {
256 0 : ADIAG_WAR(
257 : "set alternate signal stack failed, info: %s, stack overflow may not be captured.",
258 : strerror(AdiagGetErrorCode()));
259 : }
260 1013 : }
261 :
262 1013 : TraStatus TraceSignalInit(void)
263 : {
264 1013 : TraceSignalSetupAltStack();
265 14182 : for (uint32_t i = 0; i < REGISTER_SIGNAL_NUM; i++) {
266 13169 : struct TraceSigAction* sigAct = &g_sigMgr.sigAct[i];
267 13169 : (void)memset_s(&sigAct->sigAct, sizeof(struct sigaction), 0, sizeof(struct sigaction));
268 : // sa_mask is per-thread: while this handler runs, block every managed
269 : // fatal signal on the *handling* thread so a second fatal signal cannot
270 : // re-enter and corrupt the handler on the same thread. (It does not
271 : // block signals on other threads — see the g_traceAltStack limitation
272 : // note above.) sigaddset is idempotent.
273 13169 : (void)sigemptyset(&(sigAct->sigAct.sa_mask));
274 184366 : for (uint32_t j = 0; j < REGISTER_SIGNAL_NUM; j++) {
275 171197 : (void)sigaddset(&(sigAct->sigAct.sa_mask), g_sigMgr.sigAct[j].signo);
276 : }
277 13169 : sigAct->sigAct.sa_sigaction = TraceSignalHandler;
278 : // SA_ONSTACK runs the handler on the alternate stack installed above.
279 13169 : sigAct->sigAct.sa_flags = SA_SIGINFO | SA_ONSTACK;
280 : }
281 :
282 1013 : if (!TraceSignalCheckEnv()) {
283 0 : return TRACE_SUCCESS;
284 : }
285 1013 : StacktraceMonitorInit();
286 1013 : atomic_store(&g_sigMgr.exitFlag, false);
287 1013 : TraceSignalSetHandleFlag(false);
288 1013 : TraStatus ret = TraceSignalRegister();
289 1013 : ADIAG_CHK_EXPR_ACTION(ret != TRACE_SUCCESS, return ret, "trace register signal failed, ret=%d.", ret);
290 993 : return TRACE_SUCCESS;
291 : }
292 :
293 1069 : void TraceSignalExit(void)
294 : {
295 1069 : atomic_store(&g_sigMgr.exitFlag, true);
296 1069 : TraceSignalUnregister();
297 1069 : StacktraceMonitorExit();
298 : // handle func is running, and is in handle process
299 1069 : while (atomic_load(&g_sigMgr.handleFlag) && (g_sigMgr.handlePid == getpid())) {
300 0 : (void)usleep(TRACE_STACK_SIGNAL_SLEEP);
301 : }
302 : // Disable the alternate signal stack, symmetric with TraceSignalInit's
303 : // TraceSignalSetupAltStack(). Avoids leaving a stale alt-stack setting on
304 : // repeated Init/Exit cycles (e.g. tests, dynamic load). sigaltstack is
305 : // per-thread; this clears it on the calling (init) thread. SS_DISABLE on a
306 : // thread that never installed an alt stack is a no-op, so this is safe even
307 : // if Init never ran.
308 : stack_t disableStack;
309 1069 : (void)memset_s(&disableStack, sizeof(disableStack), 0, sizeof(disableStack));
310 1069 : disableStack.ss_flags = SS_DISABLE;
311 1069 : (void)sigaltstack(&disableStack, NULL);
312 1069 : }
|