“Mini Dumps” 指的是一种精简的 core dump(核心转储)机制,目的是在 高性能系统(如 Pure Storage FlashBlade)中,在出错时收集足够的调试信息,同时避免完整 core dump 带来的性能开销或空间浪费。
Core dump 是操作系统在程序崩溃时写出的一份进程内存快照,供开发者排查问题。但:
Mini dump ≈ core dump 的“精简版”
特性 | 描述 |
---|---|
智能筛选 | 只保留调试必要的数据(如崩溃线程的栈帧、寄存器、特定对象) |
体积小 | 几 MB 到几十 MB |
快速写出 | 快速转储,最小影响运行时 |
足够排查 | 能满足多数故障诊断场景 |
安全合规 | 减少用户数据泄露风险 |
FlashBlade 是一个分布式、并发性极高的系统,特点:
FlashBlade 更倾向于使用 “Mini Dump” 方案,快速记录关键状态,并自动上报分析,而非阻塞性地写出完整 core 文件。
SIGSEGV
、SIGABRT
)minidump
或 LLVM 的 llvm-symbolizer
/proc/self/maps
只采集 .text
, .data
, .stack
gdb
或 lldb
+ 符号文件调试:gdb -c mini.dmp my_binary
Mini Dump 是为大规模系统稳定运行设计的一种“轻量级核心转储”,能快速捕获崩溃关键信息,而不会拖垮系统性能或存储资源。
这段内容讲的是为什么需要 Mini Dumps(精简 core dump) ——它是从工程实用角度出发,为了改善传统 Linux core dump 的问题。
gdb
可以用 core dump 查看崩溃现场(变量、堆栈、指令地址等)。问题 | 描述 |
---|---|
隐私泄露 | dump 会包含整个内存,可能有 客户数据、密码、私钥 等敏感信息 |
非常慢 | 写出所有内容通常 需要 30 秒或更久,影响正常服务 |
非常大 | 一个系统的 dump 可高达 50GB+,严重消耗磁盘空间或网络传输时间 |
无法控制内容 | kernel 自动转储,无法选择性排除某些数据区域 |
目标 | 解释 |
---|---|
更快的 dump | 几秒内生成 |
更小的文件 | 只包含调试必要信息,几 MB 到几十 MB |
有选择性地排除区域 | 比如跳过客户数据/缓存等无关区域 |
仍能用 gdb 分析 | mini dump 不能是自定义格式,还得兼容 GDB 分析器 |
传统 core dump 优点 | 缺点 |
---|---|
可用 gdb 调试 | 太大、太慢、太危险(含敏感数据) |
完整崩溃现场信息 | 无法控制 dump 内容 |
因此,我们希望能生成一个“可被 GDB 分析的、但更小、更安全、更快的 dump”——这就是 Mini Dump。
MADV_DONTDUMP
来排除内存区域?MADV_DONTDUMP
?madvise(addr, length, MADV_DONTDUMP)
是一个 Linux 系统调用,用于标记指定的内存区域在程序崩溃时 不要写入 core dump。
听起来似乎正好符合“减少 dump 大小”的需求,但这段说明了 为什么它不适合用在实际大型系统中,尤其是 FlashBlade 这类性能敏感的环境中。
原因 | 解释 |
---|---|
调用开销大 | 这个系统调用在 dump 时是不安全的(不能临时调用), 所以必须在程序运行期间反复调用它来设置区域 → 增加运行时负担。 |
需要加锁 | 内核在处理 MADV_DONTDUMP 时必须 获取锁来保护数据结构(可能影响性能)。 |
和内存分配器有冲突 | 应用使用大页(2MB)批量分配 64MB 块,这些块会被频繁复用, 所以哪些区域该 dump 是动态变化的,用 MADV_DONTDUMP 很难精确控制。 |
粒度太粗 | MADV_DONTDUMP 是按页(≥4KB)生效,但有时候你一个页面里既有重要系统状态,也有客户数据 → 你不能全扔也不能全保。 |
最终仍然太大 | 即使用了 MADV_DONTDUMP ,也可能还会有 几 GB 的数据 被 dump,而你可能只关心其中很少部分。 |
虽然 MADV_DONTDUMP
是内核提供的排除机制,但它:
core
)内部到底包含了什么?当程序崩溃时,系统可以将其内存状态、寄存器状态等保存到一个文件中,叫做 core file(core dump)。
这个文件可以被 gdb
等工具读取和分析,帮助你 重现崩溃时的上下文状态。
你可以用这个命令查看 core 文件结构:
readelf -a core
区域 | 内容 |
---|---|
ELF header | Core 文件是 ELF 格式:e_type = ET_CORE 表示这是一个 core 文件e_machine = x86_64 表示适用于哪种架构 |
Program headers (PHDRs) | 描述内存映射等内容,每个 header 可能是: - PT_NOTE : 包含元信息(进程、线程、信号)- PT_LOAD : 真实内存数据段 |
Section headers | Core dump 通常 没有 section headers(section hdr cnt = 0) 因为这些对调试不是必须的 |
Notes 区域 (PT_NOTE) | 这是最重要的元信息区域,类型为 NT_* 开头的结构: |
Note 类型 | 含义 |
---|---|
NT_PRPSINFO |
进程信息:uid, gid, pid 等 |
NT_SIGINFO |
崩溃的信号信息:signo , errno 等(例如 SIGSEGV) |
NT_AUXV |
辅助向量(auxv_t),来自内核的启动参数 |
NT_FILE |
映射的文件区域列表(类似 /proc/self/maps )包含: start/end address , file offset , 路径等 |
NT_PRSTATUS |
每个线程的状态(tid , 当前寄存器值等) |
NT_FPREGSET |
浮点寄存器状态(x87) |
NT_X86_XSTATE |
扩展寄存器(例如 AVX、SSE、XSAVE) |
PT_LOAD
表示一段真实的内存映射区域
vaddr
)p_memsz
)p_filesz
)p_offset
)p_filesz = 0
表示该内存段被“标记”了,但并没有保存具体内容(例如被裁剪了)。Core File (ELF)
├── ELF Header
├── Program Headers
│ ├── PT_NOTE (metadata)
│ │ ├── NT_PRPSINFO
│ │ ├── NT_SIGINFO
│ │ ├── NT_AUXV
│ │ ├── NT_FILE
│ │ ├── NT_PRSTATUS (per thread)
│ │ └── ...
│ └── PT_LOAD (memory segment)
│ └── contains actual memory content (heap, stack, etc)
└── No section headers
ulimit -c unlimited
./your_app_crashing
readelf -a core
或者用 GDB 分析:
gdb your_app core
当程序出现严重错误时,操作系统会发送一个信号,比如:
信号 | 意义 |
---|---|
SIGSEGV |
段错误(非法内存访问) |
SIGABRT |
调用了 abort() |
SIGINT |
中断(比如 Ctrl+C) |
SIGILL |
非法指令执行 |
你可以**注册一个信号处理器(handler)**来在程序崩溃前做点事情,比如打印日志、生成 mini core dump 等。 |
参考:man 7 signal
文档,有一个非常重要的限制:
这些函数是明确可以在信号处理器中调用的,包括:
类别 | 可用函数 |
---|---|
文件操作 | open() , read() , write() , close() , fsync() |
进程信息 | getpid() , kill() , signal() |
字符串 | strlen() , strerror_r() |
内存 | 不能调用 malloc() 或 new ,也不能使用 STL 容器 |
输出 | 可以 write(2, ...) 输出错误信息到 stderr |
时间 | time() 、gettimeofday() 是安全的 |
禁止事项 | 原因 |
---|---|
malloc() / free() |
不是异步安全的,可能内部持有锁,会死锁 |
C++ 异常抛出 | 不合法,在 handler 里抛异常会直接终止 |
STL 容器使用 | 所有 std::vector / std::string 都依赖动态分配 |
printf() / std::cout |
会触发缓冲区刷新和 malloc ,不安全 |
在 signal handler 中你不能做复杂逻辑,但是:
open()
创建一个文件write()
写入一些你需要的信息:
/proc/self/maps
获取)close()
退出即可#include
#include
#include
#include
void handler(int sig) {
const char msg[] = "Fatal signal received, dumping...\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
// 假设打开文件并写入关键内存块等
int fd = open("/tmp/mini_dump.raw", O_CREAT | O_WRONLY, 0644);
if (fd >= 0) {
write(fd, "DUMP", 4); // 模拟
close(fd);
}
_exit(1); // 直接退出,避免进入未知状态
}
int main() {
signal(SIGSEGV, handler);
signal(SIGABRT, handler);
// 故意制造崩溃
*(int*)0 = 42;
return 0;
}
可做 | 不可做 |
---|---|
写文件(write、open) | malloc / new |
输出错误信息 | std::string、std::vector |
保存寄存器快照 | 抛异常 |
写 mini core dump | std::cout / printf |
下面是改进目标:
/proc/self/maps
映射信息(可用 gdb 分析)handler
示例代码#include
#include
#include
#include
#include
#include
#include
void handler(int sig) {
const char msg[] = "Fatal signal received, dumping...\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
int fd = open("/tmp/mini_dump.raw", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
_exit(1);
}
// 1. 写信号类型
write(fd, "SIGNAL:\n", 8);
dprintf(fd, "%d\n", sig);
// 2. 写进程 ID
write(fd, "PID:\n", 5);
dprintf(fd, "%d\n", getpid());
// 3. 写当前栈顶地址及内容(栈的“快照”)
write(fd, "STACK (partial):\n", 18);
void* sp = __builtin_frame_address(0); // 栈帧指针
write(fd, &sp, sizeof(sp));
write(fd, "STACK DATA:\n", 12);
write(fd, sp, 128); // 写入栈顶附近128字节(注意可能非法)
// 4. 写当前映射文件内容:/proc/self/maps
write(fd, "\nMEMORY MAP:\n", 13);
int maps_fd = open("/proc/self/maps", O_RDONLY);
if (maps_fd >= 0) {
char buf[256];
ssize_t r;
while ((r = read(maps_fd, buf, sizeof(buf))) > 0) {
write(fd, buf, r);
}
close(maps_fd);
}
close(fd);
_exit(1);
}
main()
保持不变:int main() {
signal(SIGSEGV, handler);
signal(SIGABRT, handler);
*(int*)0 = 42; // 故意崩溃
return 0;
}
g++ -g -o mini_dump mini_dump.cpp
./mini_dump
输出:
Fatal signal received, dumping...
Segmentation fault (core dumped)
你会看到文件 /tmp/mini_dump.raw
包含如下内容:
SIGNAL:
11
PID:
12345
STACK (partial):
[二进制数据]
MEMORY MAP:
00400000-00401000 r-xp ... main
...
/proc/self/maps
)和实际内存数据,写入 dump 文件当前只用 __builtin_frame_address(0)
得到栈帧指针,没抓寄存器。信号处理函数能拿到 ucontext_t
,里面含寄存器完整信息。
void handler(int sig, siginfo_t *info, void *ucontext) {
ucontext_t *uc = (ucontext_t *)ucontext;
// 以 x86_64 为例,打印 RIP, RSP, RBP 寄存器
uintptr_t rip = uc->uc_mcontext.gregs[REG_RIP];
uintptr_t rsp = uc->uc_mcontext.gregs[REG_RSP];
uintptr_t rbp = uc->uc_mcontext.gregs[REG_RBP];
dprintf(fd, "RIP: %p\nRSP: %p\nRBP: %p\n", (void*)rip, (void*)rsp, (void*)rbp);
}
sigaction
并使用 SA_SIGINFO
能拿到更多信息(如 siginfo_t
和 ucontext_t
):
struct sigaction sa;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
你直接读栈指针附近的128字节,有非法地址风险会导致二次崩溃。改为:
/proc/self/maps
判断栈地址范围mincore()
、mprotect()
探测可读区域SIGUSR1
给所有其他线程,让它们写寄存器和栈信息(可先写日志或预留共享内存区)pthread_kill()
发送信号,避免崩溃时死锁/proc/self/maps
和内存内容到dump文件malloc
、printf
等不可在信号处理器调用,dprintf
和write
相对安全#include
#include
#include
#include
#include
#include
#include
void handler(int sig, siginfo_t *info, void *ucontext) {
int fd = open("/tmp/mini_dump.raw", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) _exit(1);
dprintf(fd, "SIGNAL:\n%d\n", sig);
dprintf(fd, "PID:\n%d\n", getpid());
ucontext_t *uc = (ucontext_t *)ucontext;
dprintf(fd, "REGISTERS:\n");
dprintf(fd, "RIP: %p\n", (void*)uc->uc_mcontext.gregs[REG_RIP]);
dprintf(fd, "RSP: %p\n", (void*)uc->uc_mcontext.gregs[REG_RSP]);
dprintf(fd, "RBP: %p\n", (void*)uc->uc_mcontext.gregs[REG_RBP]);
// 其他寄存器...
// 尝试写栈内容(假设128字节安全)
void *rsp = (void*)uc->uc_mcontext.gregs[REG_RSP];
dprintf(fd, "STACK DATA:\n");
if (rsp != NULL) {
// 小心访问,真实项目中需要判断可读范围
write(fd, rsp, 128);
}
// 写内存映射
dprintf(fd, "\nMEMORY MAP:\n");
int maps_fd = open("/proc/self/maps", O_RDONLY);
if (maps_fd >= 0) {
char buf[256];
ssize_t r;
while ((r = read(maps_fd, buf, sizeof(buf))) > 0) {
write(fd, buf, r);
}
close(maps_fd);
}
close(fd);
_exit(1);
}
int main() {
struct sigaction sa;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
*(int*)0 = 42; // 故意崩溃
return 0;
}
sigaction
+ SA_SIGINFO
获取完整寄存器和信号信息/proc/self/maps
记录进程内存映射,方便调试用线程局部存储(TLS)自动登记tid,且在pthread_create
时hook登记。
#include
#include
#include
#include
static std::atomic<int> g_thread_count{0};
static pthread_t g_threads[MAX_THREADS];
static pid_t g_tids[MAX_THREADS];
// 线程局部存储变量
static __thread int g_my_index = -1;
void register_current_thread() {
int idx = g_thread_count.fetch_add(1);
if (idx < MAX_THREADS) {
g_threads[idx] = pthread_self();
g_tids[idx] = syscall(SYS_gettid);
g_my_index = idx;
}
}
void* thread_start_wrapper(void* (*start_routine)(void*), void* arg) {
register_current_thread();
return start_routine(arg);
}
int pthread_create_wrapped(pthread_t* thread, const pthread_attr_t* attr,
void* (*start_routine)(void*), void* arg) {
// 包装线程入口函数,使其先登记线程信息
struct WrapperArg {
void* (*start_routine)(void*);
void* arg;
};
WrapperArg* warg = new WrapperArg{start_routine, arg};
auto wrapper = [](void* warg_void) -> void* {
WrapperArg* w = (WrapperArg*)warg_void;
register_current_thread();
void* ret = w->start_routine(w->arg);
delete w;
return ret;
};
return pthread_create(thread, attr, wrapper, warg);
}
不同架构寄存器数量不同,比如 x86_64 寄存器集:
RIP, RSP, RBP, RAX, RBX, RCX, RDX, RSI, RDI, R8-R15
等。RSP
)和栈底地址确定栈范围(比如取8KB)#include
bool is_address_mapped(void* addr) {
// 简单方法:用mincore探测页是否在内存中
unsigned char vec;
void* page = (void*)((uintptr_t)addr & ~(4095));
if (mincore(page, 4096, &vec) == 0)
return true;
return false;
}
void scan_stack_for_pointers(void* stack_start, size_t stack_size,
std::vector<void*>& anchor_addrs) {
uintptr_t* ptr = (uintptr_t*)stack_start;
uintptr_t* end = (uintptr_t*)((uintptr_t)stack_start + stack_size);
for (; ptr < end; ++ptr) {
uintptr_t val = *ptr;
if (val > 0x10000 && is_address_mapped((void*)val)) {
anchor_addrs.push_back((void*)val);
}
}
}
std::set
来存储所有锚点基地址#include
#include
std::set<uintptr_t> visited_anchors;
std::queue<uintptr_t> anchor_queue;
void add_anchor(uintptr_t addr) {
// 对齐4K页
addr &= ~(4095);
if (visited_anchors.insert(addr).second) {
anchor_queue.push(addr);
}
}
void recursive_anchor_dump(int fd) {
while (!anchor_queue.empty()) {
uintptr_t base = anchor_queue.front();
anchor_queue.pop();
// Dump内存[base - 1k, base + 8k]
uintptr_t dump_start = base > 1024 ? base - 1024 : base;
size_t dump_size = 1024 + 8192;
// 读取内存
char buf[dump_size];
memcpy(buf, (void*)dump_start, dump_size);
// 写入dump文件(简略)
write(fd, &dump_start, sizeof(dump_start));
write(fd, buf, dump_size);
// 递归扫描这段内存,找新指针加入anchor_queue
uintptr_t* ptr = (uintptr_t*)buf;
uintptr_t* end = (uintptr_t*)(buf + dump_size);
for (; ptr < end; ++ptr) {
uintptr_t val = *ptr;
if (val > 0x10000 && is_address_mapped((void*)val)) {
add_anchor(val);
}
}
}
}
信号处理函数里不能使用普通锁,常见做法:
std::atomic
变量或信号量实现std::atomic<int> threads_done_count{0};
void other_thread_handler(int sig, siginfo_t*, void* uc_void) {
// 保存寄存器等
// ...
threads_done_count.fetch_add(1);
// 等待主线程通知退出信号处理
while (!g_crash_handling_done) {
usleep(100000);
}
_exit(0);
}
void crash_handler(...) {
threads_done_count = 0;
signal_other_threads();
// 等待其他线程完成
while (threads_done_count.load() < g_thread_count.load() - 1) {
usleep(100000);
}
// 继续写dump
// ...
g_crash_handling_done = true;
}
FILE HEADER
THREAD INFO BLOCK (tid, registers, stack range, stack data)
ANCHOR MEMORY BLOCKS (address, size, raw data)
PROCESS MAPS BLOCK (parsed /proc/self/maps)
你写入每个block前加长度和标识符,方便后续解析。
这已经是非常大的系统工程,建议你:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// 最大线程数
#define MAX_THREADS 32
// 全局线程信息结构
struct ThreadDumpInfo {
pid_t tid;
pthread_t ptid;
volatile sig_atomic_t signaled = 0;
ucontext_t context;
volatile sig_atomic_t done = 0;
};
static ThreadDumpInfo g_thread_infos[MAX_THREADS];
static std::atomic<int> g_thread_count{0};
static volatile sig_atomic_t g_handling_crash = 0;
static volatile sig_atomic_t g_crash_done = 0;
// 简单信号安全打印
static void sig_safe_print(const char* msg) { write(STDERR_FILENO, msg, strlen(msg)); }
// 注册当前线程
void register_thread() {
pid_t tid = syscall(SYS_gettid);
pthread_t ptid = pthread_self();
int idx = g_thread_count.fetch_add(1);
if (idx < MAX_THREADS) {
g_thread_infos[idx].tid = tid;
g_thread_infos[idx].ptid = ptid;
}
}
// 发送SIGUSR1给其他线程
void signal_other_threads() {
pid_t self_tid = syscall(SYS_gettid);
for (int i = 0; i < g_thread_count; ++i) {
if (g_thread_infos[i].tid != self_tid) {
pthread_kill(g_thread_infos[i].ptid, SIGUSR1);
}
}
}
// 其他线程信号处理器,收到SIGUSR1后保存寄存器状态
void other_thread_handler(int sig, siginfo_t*, void* uc_void) {
pid_t tid = syscall(SYS_gettid);
ucontext_t* uc = (ucontext_t*)uc_void;
for (int i = 0; i < g_thread_count; ++i) {
if (g_thread_infos[i].tid == tid) {
g_thread_infos[i].signaled = sig;
memcpy(&g_thread_infos[i].context, uc, sizeof(ucontext_t));
g_thread_infos[i].done = 1;
break;
}
}
// 等待主线程写完dump文件后退出
while (!g_crash_done) {
usleep(100000);
}
_exit(0);
}
// 写寄存器到fd,简化只写RIP寄存器(x86_64)
void dump_registers(int fd, ucontext_t* uc) {
#if defined(__x86_64__)
char buf[128];
int len = snprintf(buf, sizeof(buf), "RIP=0x%llx\n",
(unsigned long long)uc->uc_mcontext.gregs[REG_RIP]);
write(fd, buf, len);
#endif
}
// 写简单栈快照128字节(栈指针附近)
void dump_stack(int fd, ucontext_t* uc) {
#if defined(__x86_64__)
void* sp = (void*)uc->uc_mcontext.gregs[REG_RSP];
write(fd, "STACK_DATA:\n", 11);
ssize_t wr = write(fd, sp, 128);
(void)wr; // 可能失败,简单忽略
#endif
}
// 崩溃信号处理器
void crash_handler(int sig, siginfo_t* si, void* uc_void) {
if (__sync_lock_test_and_set(&g_handling_crash, 1)) {
_exit(1); // 防止重入
}
sig_safe_print("Fatal signal received. Writing mini dump...\n");
int fd = open("/tmp/minidump.raw", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
_exit(1);
}
// 写信号和进程信息
dprintf(fd, "SIGNAL: %d\n", sig);
dprintf(fd, "PID: %d\n", getpid());
dprintf(fd, "CRASH_THREAD_TID: %d\n", syscall(SYS_gettid));
// 写崩溃线程寄存器和栈
ucontext_t* uc = (ucontext_t*)uc_void;
dump_registers(fd, uc);
dump_stack(fd, uc);
// 通知其他线程写寄存器栈
signal_other_threads();
// 等待其他线程完成
for (int i = 0; i < 50; ++i) {
bool all_done = true;
for (int j = 0; j < g_thread_count; ++j) {
if (g_thread_infos[j].tid != syscall(SYS_gettid) && g_thread_infos[j].done == 0) {
all_done = false;
break;
}
}
if (all_done) break;
usleep(100000);
}
// 写其他线程寄存器简略信息
write(fd, "\nOTHER_THREADS:\n", 15);
for (int i = 0; i < g_thread_count; ++i) {
if (g_thread_infos[i].tid != syscall(SYS_gettid) && g_thread_infos[i].done) {
dprintf(fd, "Thread TID: %d\n", g_thread_infos[i].tid);
dump_registers(fd, &g_thread_infos[i].context);
// 栈数据写略
}
}
// 读取 /proc/self/maps 写入
write(fd, "\nPROC_SELF_MAPS:\n", 16);
int maps_fd = open("/proc/self/maps", O_RDONLY);
if (maps_fd >= 0) {
char buf[256];
ssize_t r;
while ((r = read(maps_fd, buf, sizeof(buf))) > 0) {
write(fd, buf, r);
}
close(maps_fd);
}
close(fd);
g_crash_done = 1;
_exit(1);
}
// 安装信号处理器
void setup_signal_handlers() {
struct sigaction sa = {};
sa.sa_sigaction = crash_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
struct sigaction sa_usr1 = {};
sa_usr1.sa_sigaction = other_thread_handler;
sigemptyset(&sa_usr1.sa_mask);
sa_usr1.sa_flags = SA_SIGINFO | SA_RESTART;
sigaction(SIGUSR1, &sa_usr1, NULL);
}
// 工作线程示范
void* worker_thread(void*) {
register_thread();
while (1) {
unsigned int ret = sleep(1);
if (ret == 0) break; // 正常睡眠完毕
// 否则被信号中断,继续睡剩余时间
}
return NULL;
}
int main() {
register_thread(); // 主线程注册一次
setup_signal_handlers();
pthread_t t1, t2;
pthread_create(&t1, NULL, worker_thread, NULL);
pthread_create(&t2, NULL, worker_thread, NULL);
sleep(1);
// 故意崩溃触发
*(volatile int*)0 = 42;
// main函数中等待线程启动
for (int i = 0; i < 10; ++i) {
if (g_thread_count.load() >= 3) break; // 主线程 + 两个工作线程
usleep(100000); // 100ms,快速轮询等待
}
return 0;
}
这是关于一种崩溃(crash)时生成内存转储(core dump / minidump)机制的讨论,结合了当前设计状态和未来规划。
/proc/self/maps
中列出,并且是以 2MB 为单位的连续块,那它肯定也对应物理内存。NT_FPREGSET
(浮点寄存器) 和 NT_X86_XSTATE
(扩展CPU状态)?这段文字说明了: