| 1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
| 2 | // |
| 3 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | |
| 5 | /* |
| 6 | build: |
| 7 | gcc -g -shared -fPIC -O0 -o libinprocdump.so inprocdump.c -ldl |
| 8 | */ |
| 9 | #include <assert.h> |
| 10 | #include <stdbool.h> |
| 11 | #include <elf.h> |
| 12 | #include <dlfcn.h> |
| 13 | #include <fcntl.h> |
| 14 | #include <inttypes.h> |
| 15 | #include <unistd.h> |
| 16 | #include <stdio.h> |
| 17 | #include <stdlib.h> |
| 18 | #include <string.h> |
| 19 | #if defined(__aarch64__) |
| 20 | #include "../aarch64/syscall.h" |
| 21 | #else |
| 22 | #include <syscall.h> |
| 23 | #endif |
| 24 | #include <sys/mman.h> |
| 25 | #include <stdarg.h> |
| 26 | #include <sys/auxv.h> |
| 27 | #include <linux/futex.h> |
| 28 | #include <sys/time.h> |
| 29 | #include <errno.h> |
| 30 | #include <sys/param.h> |
| 31 | #include <sys/socket.h> |
| 32 | #include <sys/prctl.h> |
| 33 | #include <sys/user.h> |
| 34 | #include <linux/audit.h> |
| 35 | #include <linux/filter.h> |
| 36 | #include <linux/unistd.h> |
| 37 | #include <linux/seccomp.h> |
| 38 | #include <time.h> |
| 39 | #include <sys/un.h> |
| 40 | |
| 41 | #include "../include/shared_mem_dump.h" |
| 42 | #include "../include/event_man.h" |
| 43 | #include "../cpu.h" |
| 44 | |
| 45 | #define SAFE_GUARD (1024) |
| 46 | |
| 47 | static int g_pid = 0; // store current pid to avoid fork affect |
| 48 | static MemoryDumper *g_dumper = NULL; |
| 49 | static unsigned char *parameter_flags = NULL; |
| 50 | struct number_set trace_set[1]; |
| 51 | static int get_stack_range(MemoryDumper *dumper, uintptr_t sp); |
| 52 | |
| 53 | long syscall_no_hook(long syscall_number, ...); |
| 54 | |
| 55 | static char debug_dumps_on = 0; |
| 56 | |
| 57 | void debug_dump(const char *fmt, ...) |
| 58 | { |
| 59 | int len; |
| 60 | va_list ap; |
| 61 | |
| 62 | if (!debug_dumps_on) |
| 63 | return; |
| 64 | |
| 65 | va_start(ap, fmt); |
| 66 | len = vsnprintf(NULL, 0, fmt, ap); |
| 67 | va_end(ap); |
| 68 | |
| 69 | if (len <= 0) |
| 70 | return; |
| 71 | |
| 72 | char buf[len + 1]; |
| 73 | |
| 74 | va_start(ap, fmt); |
| 75 | len = vsprintf(buf, fmt, ap); |
| 76 | va_end(ap); |
| 77 | |
| 78 | syscall_no_hook(SYS_write, 2, buf, len); |
| 79 | } |
| 80 | |
| 81 | #if defined(__x86_64__) |
| 82 | #ifndef SYS_pkey_free |
| 83 | #define SYS_pkey_free (331) |
| 84 | #endif |
| 85 | |
| 86 | static unsigned char syscall_buf[ |
| 87 | (SYS_pkey_free - __NR_Linux + 1) * 2 + |
| 88 | (SYS_pkey_free - __NR_Linux + 1 + 7) / 8 + 256]; |
| 89 | |
| 90 | __asm ( |
| 91 | ".global syscall_no_hook\n\t" |
| 92 | ".type syscall_no_hook, @function\n\t" |
| 93 | ".text\n\t" |
| 94 | "syscall_no_hook:\n\t" |
| 95 | "movq %rdi, %rax\n\t" /* convert from linux ABI calling */ |
| 96 | "movq %rsi, %rdi\n\t" /* convention to syscall calling convention */ |
| 97 | "movq %rdx, %rsi\n\t" |
| 98 | "movq %rcx, %rdx\n\t" |
| 99 | "movq %r8, %r10 \n\t" |
| 100 | "movq %r9, %r8\n\t" |
| 101 | "movq 8(%rsp), %r9\n\t" |
| 102 | "syscall\n\t" |
| 103 | "ret\n\t" |
| 104 | ".size syscall_no_hook, .-syscall_no_hook\n\t" |
| 105 | ); |
| 106 | |
| 107 | #elif defined(__sw_64) |
| 108 | #ifndef SYS_execveat |
| 109 | #define SYS_execveat (513) |
| 110 | #endif |
| 111 | static unsigned char syscall_buf[ |
| 112 | (SYS_execveat - __NR_Linux + 1) * 2 + |
| 113 | (SYS_execveat - __NR_Linux + 1 + 7) / 8 + 256]; |
| 114 | |
| 115 | __asm( |
| 116 | ".global syscall_no_hook\n\t" |
| 117 | ".type syscall_no_hook, @function\n\t" |
| 118 | ".text\n\t" |
| 119 | "syscall_no_hook:\n\t" |
| 120 | "bis $31, $16, $0\n\t" |
| 121 | "bis $31, $17, $16\n\t" |
| 122 | "bis $31, $18, $17\n\t" |
| 123 | "bis $31, $19, $18\n\t" |
| 124 | "bis $31, $20, $19\n\t" |
| 125 | "bis $31, $21, $20\n\t" |
| 126 | "ldl $21, 0($30)\n\t" // 6th argument of syscall |
| 127 | "sys_call 0x83\n\t" // sys_call never change ra register |
| 128 | "ret $31, ($26), 0x1\n\t" |
| 129 | ".size syscall_no_hook, .-syscall_no_hook\n\t" |
| 130 | ); |
| 131 | |
| 132 | #elif defined(__mips64) |
| 133 | #ifndef SYS_pkey_free |
| 134 | #define SYS_pkey_free (325 + __NR_Linux) |
| 135 | #endif |
| 136 | |
| 137 | static unsigned char syscall_buf[ |
| 138 | (SYS_pkey_free - __NR_Linux + 1) * 2 + |
| 139 | (SYS_pkey_free - __NR_Linux + 1 + 7) / 8 + 256]; |
| 140 | |
| 141 | __asm( |
| 142 | ".globl syscall_no_hook\n\t" |
| 143 | "syscall_no_hook:\n\t" |
| 144 | "move $v0, $a0\n\t" |
| 145 | "move $a0, $a1\n\t" |
| 146 | "move $a1, $a2\n\t" |
| 147 | "move $a2, $a3\n\t" |
| 148 | "move $a3, $a4\n\t" |
| 149 | "move $a4, $a5\n\t" |
| 150 | "move $a5, $a6\n\t" |
| 151 | "syscall\n\t" |
| 152 | "jr $ra\n\t" |
| 153 | "nop\n\t" |
| 154 | ); |
| 155 | #elif defined(__aarch64__) |
| 156 | #ifndef SYS_fork |
| 157 | #define SYS_fork (1079 + __NR_Linux) |
| 158 | #endif |
| 159 | |
| 160 | static unsigned char syscall_buf[ |
| 161 | (SYS_fork - __NR_Linux + 1) * 2 + |
| 162 | (SYS_fork - __NR_Linux + 1 + 7) / 8 + 256]; |
| 163 | |
| 164 | __asm( |
| 165 | ".globl syscall_no_hook\n\t" |
| 166 | "syscall_no_hook:\n\t" |
| 167 | "mov x8, x0\n\t" |
| 168 | "mov x0, x1\n\t" |
| 169 | "mov x1, x2\n\t" |
| 170 | "mov x2, x3\n\t" |
| 171 | "mov x3, x4\n\t" |
| 172 | "mov x4, x5\n\t" |
| 173 | "mov x5, x6\n\t" |
| 174 | "svc #0x0\n\t" |
| 175 | "ret\n\t" |
| 176 | "nop \n\t" |
| 177 | "nop\n\t" |
| 178 | "nop\n\t" |
| 179 | "nop\n\t" |
| 180 | "nop\n\t" |
| 181 | "nop\n\t" |
| 182 | "nop\n\t" |
| 183 | ); |
| 184 | #else |
| 185 | #error need define new arch |
| 186 | #endif |
| 187 | |
| 188 | static unsigned char number_isset(const unsigned int i, const number_slot_t *const vec) |
| 189 | { |
| 190 | return (vec[i / BITS_PER_SLOT] & |
| 191 | ((number_slot_t)1 << (i % BITS_PER_SLOT))) > 0; |
| 192 | } |
| 193 | |
| 194 | unsigned char is_number_in_set(const unsigned number) |
| 195 | { |
| 196 | if (0 == trace_set[0].nslots) { |
| 197 | return trace_set[0].not_flag; |
| 198 | } |
| 199 | return (((number / BITS_PER_SLOT < trace_set[0].nslots) && |
| 200 | number_isset(number, trace_set[0].vec)) ^ trace_set[0].not_flag); |
| 201 | } |
| 202 | |
| 203 | static int open_socket(int *sock_fd) |
| 204 | { |
| 205 | struct sockaddr_un addr; |
| 206 | int sc, rc = -1; |
| 207 | |
| 208 | *sock_fd = syscall_no_hook(SYS_socket, AF_UNIX, SOCK_STREAM, 0); |
| 209 | if (*sock_fd == -1) { |
| 210 | debug_dump("failed to create socket: %s\n" , strerror(errno)); |
| 211 | goto done; |
| 212 | } |
| 213 | |
| 214 | memset(&addr, 0, sizeof(addr)); |
| 215 | addr.sun_family = AF_UNIX; |
| 216 | snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, |
| 217 | SHARED_SOCKET_NAME, g_pid); |
| 218 | debug_dump("socket: %s -> %d\n" , addr.sun_path, *sock_fd); |
| 219 | |
| 220 | sc = syscall_no_hook(SYS_connect, |
| 221 | *sock_fd, (struct sockaddr *)&addr, sizeof(addr)); |
| 222 | if (sc == -1) { |
| 223 | syscall_no_hook(SYS_close, *sock_fd); |
| 224 | debug_dump("failed to connect: %s\n" , strerror(errno)); |
| 225 | goto done; |
| 226 | } |
| 227 | |
| 228 | rc = 0; |
| 229 | |
| 230 | done: |
| 231 | return rc; |
| 232 | } |
| 233 | |
| 234 | int send_cmd(int cmd, uintptr_t arg) |
| 235 | { |
| 236 | char buf[20]; |
| 237 | int sock_fd = 0; |
| 238 | int nbytes = 0; |
| 239 | |
| 240 | *(int *)&buf[0] = cmd; |
| 241 | *(uintptr_t *)&buf[4] = arg; |
| 242 | |
| 243 | if (open_socket(&sock_fd) < 0) { |
| 244 | return -1; |
| 245 | } |
| 246 | |
| 247 | nbytes = syscall_no_hook(SYS_write, |
| 248 | sock_fd, buf, sizeof(int) + sizeof(uintptr_t)); |
| 249 | debug_dump("send_cmd:%d, %lx->%d\n" , cmd, arg, nbytes); |
| 250 | |
| 251 | // get result |
| 252 | *(int *)&buf[0] = -1; |
| 253 | nbytes = syscall_no_hook(SYS_read, sock_fd, buf, sizeof(int)); |
| 254 | syscall_no_hook(SYS_close, sock_fd); |
| 255 | |
| 256 | return *(int *)&buf[0]; |
| 257 | } |
| 258 | |
| 259 | MemoryDumper *get_memory_dumper(void) |
| 260 | { |
| 261 | return g_dumper; |
| 262 | } |
| 263 | |
| 264 | MemoryDumper *create_memory_dumper(void) |
| 265 | { |
| 266 | char path[256]; |
| 267 | int fd = 0; |
| 268 | void *map_addr = NULL; |
| 269 | int flags = MAP_SHARED; |
| 270 | MemoryDumper *dumper = NULL; |
| 271 | int size = 0; |
| 272 | char *data = NULL; |
| 273 | char *shared_buffer_size = NULL; |
| 274 | int nonce = send_cmd(SYS_share_name, 0); |
| 275 | if (nonce < 0) { |
| 276 | return NULL; |
| 277 | } |
| 278 | |
| 279 | snprintf(path, sizeof(path) - 1, |
| 280 | SHARED_FILE_NAME, g_pid, nonce); |
| 281 | shared_buffer_size = getenv("ST2_SYSCALL_BUFFER_SIZE" ); |
| 282 | if (shared_buffer_size) { |
| 283 | size = atoi(shared_buffer_size); |
| 284 | if (size < SHARED_BUFFER_SIZE) size = SHARED_BUFFER_SIZE; |
| 285 | } else { |
| 286 | size = SHARED_BUFFER_SIZE; |
| 287 | } |
| 288 | |
| 289 | #if defined (__aarch64__) |
| 290 | fd = syscall_no_hook(SYS_openat, AT_FDCWD, path, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600); |
| 291 | #else |
| 292 | fd = syscall_no_hook(SYS_open, path, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600); |
| 293 | #endif |
| 294 | if (fd < 0) { |
| 295 | return NULL; // xabort_errno(errno, "Failed to open shmem file!\n"); |
| 296 | } |
| 297 | debug_dump("shmem file:%s, size=%d\n" , path, size); |
| 298 | |
| 299 | send_cmd(SYS_init_buffers, size); |
| 300 | |
| 301 | map_addr = (void *)syscall_no_hook(SYS_mmap, |
| 302 | NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); |
| 303 | syscall_no_hook(SYS_close, fd); |
| 304 | // Remove the fs name so that we don't have to worry about |
| 305 | // cleaning up this segment in error conditions. |
| 306 | syscall_no_hook(SYS_unlink, path); |
| 307 | |
| 308 | if (MAP_FAILED == map_addr) { |
| 309 | return NULL; // xabort_errno(errno, "Failed to mmap shmem region\n!"); |
| 310 | } |
| 311 | |
| 312 | dumper = (MemoryDumper *)map_addr; |
| 313 | |
| 314 | dumper->pid = g_pid; |
| 315 | dumper->mutex = 1; |
| 316 | assert(dumper->size + sizeof(*dumper) == (unsigned)size); |
| 317 | |
| 318 | // load syscall filter set |
| 319 | trace_set[0].nslots = dumper->syscall.nslots; |
| 320 | trace_set[0].not_flag = dumper->syscall.not_flag; |
| 321 | data = BUFFER_HEAD(dumper); |
| 322 | size = sizeof(number_slot_t) * trace_set[0].nslots; |
| 323 | if (trace_set[0].nslots > 0) { |
| 324 | trace_set[0].vec = (number_slot_t *)&syscall_buf[0]; |
| 325 | memcpy(trace_set[0].vec, data, size); |
| 326 | data += size; |
| 327 | } else { |
| 328 | trace_set[0].vec = NULL; |
| 329 | } |
| 330 | parameter_flags = &syscall_buf[0] + size; |
| 331 | memcpy(parameter_flags, data + sizeof(int), *(int *)data); |
| 332 | |
| 333 | send_cmd(SYS_enable_dump, 0); |
| 334 | |
| 335 | g_dumper = dumper; |
| 336 | |
| 337 | return dumper; |
| 338 | } |
| 339 | |
| 340 | void lock_buffers(MemoryDumper *dumper) |
| 341 | { |
| 342 | int s; |
| 343 | // int tid = syscall_no_hook(SYS_gettid); |
| 344 | // debug_dump("[%d] try to lock\n", tid); |
| 345 | for (;;) { |
| 346 | // returns true if the comparison is successful and newval was written |
| 347 | if (__sync_bool_compare_and_swap(&dumper->mutex, 1, 0)) { |
| 348 | // debug_dump("[%d] lock ok\n", tid); |
| 349 | break; |
| 350 | } |
| 351 | |
| 352 | /* Futex is not available; wait */ |
| 353 | s = syscall_no_hook(SYS_futex, |
| 354 | &dumper->mutex, FUTEX_WAIT, 0, NULL, NULL, 0); |
| 355 | if (s == -1 && errno != EAGAIN) { |
| 356 | // debug_dump("[%d] FUTEX_WAIT failed, errno=%d\n", tid, errno); |
| 357 | break; |
| 358 | } |
| 359 | // try again for there are more than one thread waited. |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | void unlock_buffers(MemoryDumper *dumper) |
| 364 | { |
| 365 | // int tid = syscall_no_hook(SYS_gettid); |
| 366 | // debug_dump("[%d] try to unlock\n", tid); |
| 367 | if (__sync_bool_compare_and_swap(&dumper->mutex, 0, 1)) { |
| 368 | if (syscall_no_hook(SYS_futex, |
| 369 | &dumper->mutex, FUTEX_WAKE, 1, NULL, NULL, 0) < 0) { |
| 370 | // debug_dump("[%d] FUTEX_WAKE failed, errno=%d\n", tid, errno); |
| 371 | } |
| 372 | else { |
| 373 | // debug_dump("[%d] unlock ok\n", tid); |
| 374 | } |
| 375 | } |
| 376 | else { |
| 377 | assert(0); |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | long get_sp(void) |
| 382 | { |
| 383 | long sp = 0; |
| 384 | #if defined(__x86_64__) |
| 385 | __asm__ volatile("\t movq %%rsp, %0" : |
| 386 | "=r" (sp)); |
| 387 | #elif defined(__sw_64) |
| 388 | __asm__ volatile("\t mov $30, %0" : |
| 389 | "=r" (sp)); |
| 390 | #elif defined(__mips64) |
| 391 | __asm__ volatile("\t move %0, $sp" : |
| 392 | "=r" (sp)); |
| 393 | #elif defined(__aarch64__) |
| 394 | __asm__ volatile("\t mov %0, sp" : |
| 395 | "=r" (sp)); |
| 396 | #endif |
| 397 | return sp; |
| 398 | } |
| 399 | |
| 400 | void get_regs(char* p) |
| 401 | { |
| 402 | #if defined(__x86_64__) |
| 403 | __asm__ volatile("\t movq %%rsp, %0" : |
| 404 | "=r" (((struct user_regs_struct *)(p))->rsp)); |
| 405 | __asm__ volatile("\t movq %%rbp, %0" : |
| 406 | "=r" (((struct user_regs_struct *)(p))->rbp)); |
| 407 | __asm__ volatile("\t lea (%%rip), %%rax\n\t movq %%rax,%0" : |
| 408 | "=r" (((struct user_regs_struct *)(p))->rip)); |
| 409 | #elif defined(__sw_64) |
| 410 | __asm__ volatile("\t mov $26, %0" : |
| 411 | "=r" (((USER_REGS *)(p))->ra)); |
| 412 | __asm__ volatile("\t mov $30, %0" : |
| 413 | "=r" (((USER_REGS *)(p))->sp)); |
| 414 | #elif defined(__mips64) |
| 415 | __asm__ volatile("\t move %0, $ra" : |
| 416 | "=r" (((USER_REGS *)(p))->ra)); |
| 417 | __asm__ volatile("\t move %0, $sp" : |
| 418 | "=r" (((USER_REGS *)(p))->sp)); |
| 419 | #elif defined(__aarch64__) |
| 420 | __asm__ volatile("\t mov %0, x30" : |
| 421 | "=r" (((USER_REGS *)(p))->ra)); |
| 422 | __asm__ volatile("\t mov %0, sp" : |
| 423 | "=r" (((USER_REGS *)(p))->sp)); |
| 424 | #endif |
| 425 | } |
| 426 | |
| 427 | void record_event_simple(MemoryDumper* dumper, int type, const char* msg, int len) |
| 428 | { |
| 429 | int tid = syscall_no_hook(SYS_gettid); |
| 430 | int msg_len = len > (int)(sizeof(long)) ? len:0; |
| 431 | char *sp = NULL; |
| 432 | char *sp_end = NULL; |
| 433 | int stack_size = 0; |
| 434 | char *walk = NULL; |
| 435 | int current = 0; |
| 436 | const int cpu_size = sizeof(USER_REGS) + sizeof(USER_FPREGS); |
| 437 | |
| 438 | lock_buffers(dumper); |
| 439 | |
| 440 | assert(msg_len <= EVENT_EXTRA_INFO_SIZE); |
| 441 | assert(DUMP_REASON_signal < type && type < DUMP_REASON_ptrace); |
| 442 | |
| 443 | current = dumper->current; |
| 444 | |
| 445 | // check stack range |
| 446 | sp = (char *)(get_sp() & (~(dumper->page_size - 1))); |
| 447 | if ((uintptr_t)sp < dumper->stack_begin || |
| 448 | (uintptr_t)sp > dumper->stack_end) { |
| 449 | stack_size = get_stack_range(dumper, (uintptr_t)sp); |
| 450 | sp_end = sp + stack_size; |
| 451 | } else { |
| 452 | sp_end = sp + dumper->max_stack_size; |
| 453 | if (sp_end > (char *)(dumper->stack_end)) { |
| 454 | sp_end = (char *)(dumper->stack_end); |
| 455 | } |
| 456 | if (sp_end > sp) stack_size = (sp_end - sp); |
| 457 | else stack_size = 0; |
| 458 | } |
| 459 | |
| 460 | if (current + stack_size + cpu_size + msg_len + SAFE_GUARD > dumper->size) { |
| 461 | send_cmd(SYS_flush_buffers, current); |
| 462 | current = 0; |
| 463 | } |
| 464 | debug_dump("dump-event begin:tid=%d,type=%d;pos=%d;\n" , |
| 465 | tid, type, current); |
| 466 | |
| 467 | walk = BUFFER_HEAD(dumper) + current; |
| 468 | syscall_no_hook(SYS_clock_gettime, CLOCK_REALTIME, (struct timespec *)walk); |
| 469 | walk += sizeof(struct timespec); |
| 470 | |
| 471 | *(short *)walk = type; |
| 472 | *(short *)(walk + 2) = 1; |
| 473 | walk += sizeof(int); |
| 474 | |
| 475 | *(int *)walk = tid | (msg_len<<16); // extra data is msg |
| 476 | walk += sizeof(int); |
| 477 | memcpy(walk, msg, msg_len); |
| 478 | walk += msg_len; |
| 479 | |
| 480 | *(short *)walk = tid; |
| 481 | *(short *)(walk + 2) = cpu_size; |
| 482 | walk += sizeof(int); |
| 483 | |
| 484 | get_regs(walk); |
| 485 | walk += cpu_size; |
| 486 | |
| 487 | // TODO: dump TLS (.tbss + .tdata, _tls_get_addr) |
| 488 | *(int *)walk = 0; |
| 489 | walk += sizeof(int); |
| 490 | |
| 491 | // stack is always zero |
| 492 | *(char **)walk = sp; |
| 493 | *(int *)(walk + sizeof(sp)) = stack_size; |
| 494 | walk += sizeof(char *) + sizeof(int); |
| 495 | if (stack_size > 0) { |
| 496 | memcpy(walk, sp, stack_size); |
| 497 | walk += stack_size; |
| 498 | } |
| 499 | |
| 500 | *(int *)walk = 0; // heap count always zero; |
| 501 | walk += sizeof(int); |
| 502 | |
| 503 | dumper->current = walk - BUFFER_HEAD(dumper); |
| 504 | assert(dumper->current < dumper->size); |
| 505 | |
| 506 | debug_dump("dump-event end:tid=%d,type=%d;pos=%d;\n" , |
| 507 | tid, type, dumper->current); |
| 508 | unlock_buffers(dumper); |
| 509 | } |
| 510 | |
| 511 | typedef struct tagVmSegment { |
| 512 | uintptr_t start; |
| 513 | uintptr_t end; |
| 514 | }VmSegment; |
| 515 | |
| 516 | static char *get_line(char *buf, int size) |
| 517 | { |
| 518 | for (int i = 0; i < size; ++i) { |
| 519 | if (buf[i] == '\n') { |
| 520 | return buf + i; |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | return NULL; |
| 525 | } |
| 526 | |
| 527 | static int get_stack_range(MemoryDumper *dumper, uintptr_t sp) |
| 528 | { |
| 529 | char buf[4096]; |
| 530 | char *line = NULL; |
| 531 | char *line_end = NULL; |
| 532 | char *stop = NULL; |
| 533 | uintptr_t start = 0; |
| 534 | uintptr_t end = 0; |
| 535 | int eof = 0; |
| 536 | int size = 256; |
| 537 | int remain = 0; |
| 538 | |
| 539 | #if defined(__aarch64__) |
| 540 | int fd = syscall_no_hook(SYS_openat, AT_FDCWD, "/proc/self/maps" , O_RDONLY); |
| 541 | #else |
| 542 | int fd = syscall_no_hook(SYS_open, "/proc/self/maps" , O_RDONLY); |
| 543 | #endif |
| 544 | if (fd <= 0) { |
| 545 | return 256; |
| 546 | } |
| 547 | |
| 548 | while (!eof) { |
| 549 | int bytes = syscall_no_hook(SYS_read, |
| 550 | fd, buf + remain, sizeof(buf) - remain); |
| 551 | if (bytes <= 0) { |
| 552 | eof = 1; |
| 553 | break; |
| 554 | } |
| 555 | remain += bytes; |
| 556 | |
| 557 | line = buf; |
| 558 | |
| 559 | while (remain > 0) { |
| 560 | line_end = get_line(line, remain); |
| 561 | if (NULL == line_end) { |
| 562 | memmove(buf, line, remain); |
| 563 | break; |
| 564 | } |
| 565 | start = strtol(line, &stop, 16); |
| 566 | end = strtol(stop + 1, &stop, 16); |
| 567 | if (start <= sp && sp <= end) { |
| 568 | eof = 1; |
| 569 | size = end - sp; |
| 570 | break; |
| 571 | } |
| 572 | remain -= (line_end + 1 - line); |
| 573 | line = line_end + 1; |
| 574 | } |
| 575 | } |
| 576 | syscall_no_hook(SYS_close, fd); |
| 577 | |
| 578 | if (size > dumper->max_stack_size) { |
| 579 | size = dumper->max_stack_size; |
| 580 | } |
| 581 | |
| 582 | return size; |
| 583 | } |
| 584 | |
| 585 | static int my_strlen(char* buf, int max) |
| 586 | { |
| 587 | int i = 0; |
| 588 | while (buf[i] > 0 && i < max) { |
| 589 | ++i; |
| 590 | } |
| 591 | |
| 592 | if (0 == buf[i]) { |
| 593 | ++i; |
| 594 | } |
| 595 | |
| 596 | return i; |
| 597 | } |
| 598 | |
| 599 | /* |
| 600 | * binary layout for one event, always 4bytes align: |
| 601 | * time(sizeof(struct timespec)), event_type(2byte), thread_num(2byte), |
| 602 | * current_tid(2byte), event_extra_size(2byte)[,event_extra_data] |
| 603 | * { |
| 604 | * tid(2byte), cpu_context_size(2byte)[, user_regs_struct+user_fpregs_struct], |
| 605 | * tls_size(4byte)[, tls_data] |
| 606 | * stack_addr(uintptr_t), stack_length(4byte)[,stack_data] |
| 607 | * } |
| 608 | * |
| 609 | * ... |
| 610 | * |
| 611 | * { |
| 612 | * tid(2byte), cpu_context_size(2byte)[, user_regs_struct+user_fpregs_struct], |
| 613 | * tls_size(4byte)[, tls_data] |
| 614 | * stack_addr(uintptr_t), stack_length(4byte)[,stack_data] |
| 615 | * } |
| 616 | * heap_count(4byte)[, addr(uintptr_t)+size(4byte)+heap_data, ...] |
| 617 | */ |
| 618 | void record_syscall(MemoryDumper *dumper, int nr, long* args, long result, void *cpu) |
| 619 | { |
| 620 | VmSegment segs[12]; |
| 621 | VmSegment *seg = &segs[0]; |
| 622 | int heap_count = 0; |
| 623 | int heap_size = 0; |
| 624 | unsigned char arg_flag = 0; |
| 625 | unsigned char arg_count = 0; |
| 626 | char *sp = NULL; |
| 627 | char *sp_end = NULL; |
| 628 | int stack_size = 0; |
| 629 | char *walk = NULL; |
| 630 | const int cpu_size = sizeof(USER_REGS) + sizeof(USER_FPREGS); |
| 631 | int current = 0; |
| 632 | int syscall_parameter_size = 0; |
| 633 | const uintptr_t mask = ~3; |
| 634 | USER_REGS *ctx = (USER_REGS*)cpu; |
| 635 | int tid = syscall_no_hook(SYS_gettid); |
| 636 | |
| 637 | lock_buffers(dumper); |
| 638 | |
| 639 | // the follow memcpy maybe crashed so store current position first! |
| 640 | current = dumper->current; |
| 641 | syscall_parameter_size = dumper->max_param_size; |
| 642 | |
| 643 | // check stack range |
| 644 | #if defined(__x86_64__) |
| 645 | sp = (char *)(ctx->rsp & (~(dumper->page_size - 1))); |
| 646 | #else |
| 647 | sp = (char *)(ctx->sp & (~(dumper->page_size - 1))); |
| 648 | #endif |
| 649 | if ((uintptr_t)sp < dumper->stack_begin || |
| 650 | (uintptr_t)sp > dumper->stack_end) { |
| 651 | stack_size = get_stack_range(dumper, (uintptr_t)sp); |
| 652 | sp_end = sp + stack_size; |
| 653 | } else { |
| 654 | sp_end = sp + dumper->max_stack_size; |
| 655 | if (sp_end > (char *)(dumper->stack_end)) { |
| 656 | sp_end = (char *)(dumper->stack_end); |
| 657 | } |
| 658 | if (sp_end > sp) stack_size = (sp_end - sp); |
| 659 | else stack_size = 0; |
| 660 | } |
| 661 | |
| 662 | // check if dump parameter of syscall |
| 663 | arg_flag = parameter_flags[2 * (nr - __NR_Linux)]; |
| 664 | arg_count = parameter_flags[2 * (nr - __NR_Linux) + 1]; |
| 665 | if ((arg_flag != 0) && (syscall_parameter_size > 0)) { |
| 666 | // parameter is void*, size indicate in next parameter |
| 667 | uint8_t size_indicate_by_next = arg_flag & 0x80; |
| 668 | for (unsigned char i = 0; i < arg_count; ++i) { |
| 669 | if (arg_flag & 1) { |
| 670 | uintptr_t addr = args[i]; |
| 671 | if ((addr > 0) && |
| 672 | ((addr < (uintptr_t)sp) || |
| 673 | (addr > (uintptr_t)sp_end))) { |
| 674 | seg->start = addr; |
| 675 | if (size_indicate_by_next) { |
| 676 | if (args[i + 1] > 0) { |
| 677 | seg->end = addr + args[i + 1]; |
| 678 | ++seg; |
| 679 | } |
| 680 | break; |
| 681 | } |
| 682 | seg->end = addr + my_strlen((char *)addr, syscall_parameter_size); |
| 683 | ++seg; |
| 684 | } |
| 685 | } |
| 686 | arg_flag >>= 1; |
| 687 | } |
| 688 | |
| 689 | if (seg != &segs[0]) { |
| 690 | heap_count = seg - &segs[0]; |
| 691 | seg = &segs[0]; |
| 692 | for (int i = 0; i < heap_count; ++i) { |
| 693 | // make size is 4byte align |
| 694 | heap_size += ((seg[i].end - seg[i].start + 3) & mask) + |
| 695 | sizeof(int) + sizeof(uintptr_t); |
| 696 | } |
| 697 | } |
| 698 | } |
| 699 | |
| 700 | if (current + stack_size + cpu_size + heap_size + SAFE_GUARD > dumper->size) { |
| 701 | send_cmd(SYS_flush_buffers, current); |
| 702 | current = 0; |
| 703 | } |
| 704 | debug_dump("dump-syscall begin:tid=%d,syscall=%d,%lx;pos=%d;stack=%p,%d;\n" , |
| 705 | tid, nr, result, current, sp, stack_size); |
| 706 | |
| 707 | walk = BUFFER_HEAD(dumper) + current; |
| 708 | syscall_no_hook(SYS_clock_gettime, CLOCK_REALTIME, (struct timespec *)walk); |
| 709 | walk += sizeof(struct timespec); |
| 710 | |
| 711 | *(short *)walk = nr; |
| 712 | *(short *)(walk + 2) = 1; |
| 713 | walk += sizeof(int); |
| 714 | |
| 715 | *(int *)walk = tid | (sizeof(long)<<16); // extra data is syscall result |
| 716 | walk += sizeof(int); |
| 717 | *(long *)walk = result; |
| 718 | walk += sizeof(long); |
| 719 | |
| 720 | *(short *)walk = tid; |
| 721 | *(short *)(walk + 2) = cpu_size; |
| 722 | walk += sizeof(int); |
| 723 | |
| 724 | memcpy(walk, ctx, sizeof(USER_REGS)); |
| 725 | #if defined(__mips64) || defined(__sw_64) |
| 726 | ((USER_REGS*)walk)->sp += 512; //see syscall_wrapper in mips64/sunway64 |
| 727 | ((USER_REGS*)walk)->ra = ctx->t2; //see syscall_wrapper in mips64/sunway64 |
| 728 | ((USER_REGS*)walk)->pc = ctx->t3; // see build_trampoline in mips64/sunway64 |
| 729 | #elif defined(__aarch64__) |
| 730 | ((USER_REGS*)walk)->sp += 512; //see syscall_wrapper in arm64 |
| 731 | ((USER_REGS*)walk)->ra = ctx->x11; //see syscall_wrapper in arm64 |
| 732 | ((USER_REGS*)walk)->pc = ctx->x10; // see build_trampoline in arm64 |
| 733 | #endif |
| 734 | walk += cpu_size; |
| 735 | |
| 736 | // TODO: dump TLS (.tbss + .tdata, _tls_get_addr) |
| 737 | *(int *)walk = 0; |
| 738 | walk += sizeof(int); |
| 739 | |
| 740 | // stack |
| 741 | *(char **)walk = sp; |
| 742 | *(int *)(walk + sizeof(sp)) = stack_size; |
| 743 | walk += sizeof(char *) + sizeof(int); |
| 744 | |
| 745 | if (stack_size > 0) { |
| 746 | memcpy(walk, sp, stack_size); |
| 747 | walk += stack_size; |
| 748 | } |
| 749 | |
| 750 | *(int *)walk = heap_count; |
| 751 | walk += sizeof(int); |
| 752 | for (int i = 0; i < heap_count; ++i) { |
| 753 | int size = seg->end - seg->start; |
| 754 | int align = ((size + 3) & mask) - size; |
| 755 | *(uintptr_t *)walk = seg->start; |
| 756 | *(int *)(walk + sizeof(uintptr_t)) = size + align; |
| 757 | memcpy(walk + sizeof(uintptr_t) + sizeof(int), |
| 758 | (void *)seg->start, size); |
| 759 | walk += size + sizeof(int) + sizeof(uintptr_t); |
| 760 | if (align > 0) { |
| 761 | memset(walk, 0xcc, align); |
| 762 | walk += align; |
| 763 | } |
| 764 | ++seg; |
| 765 | } |
| 766 | |
| 767 | dumper->current = walk - BUFFER_HEAD(dumper); |
| 768 | assert(dumper->current < dumper->size); |
| 769 | ++dumper->syscall_count; |
| 770 | |
| 771 | debug_dump("dump-syscall end:tid=%d,syscall=%d;pos=%d;heap_count=%d;\n" , |
| 772 | tid, nr, dumper->current, heap_count); |
| 773 | |
| 774 | unlock_buffers(dumper); |
| 775 | } |
| 776 | |
| 777 | static __attribute__((constructor)) void init_func(int argc, char **argv) |
| 778 | { |
| 779 | (void)argc; |
| 780 | |
| 781 | if (!strcmp(argv[0], "gdb" )) { |
| 782 | return; |
| 783 | } |
| 784 | |
| 785 | #if defined(__sw_64) |
| 786 | g_pid = syscall_no_hook(SYS_getxpid); |
| 787 | #else |
| 788 | g_pid = syscall_no_hook(SYS_getpid); |
| 789 | #endif |
| 790 | |
| 791 | if (getenv("ST2_DEBUG_DUMP" )) { |
| 792 | debug_dumps_on = 1; |
| 793 | debug_dump("libinprocdump start g_pid=%d\n" , g_pid); |
| 794 | } |
| 795 | } |
| 796 | |