Linux์˜ Strace: ์—ญ์‚ฌ, ์„ค๊ณ„ ๋ฐ ์‚ฌ์šฉ

Linux์˜ Strace: ์—ญ์‚ฌ, ์„ค๊ณ„ ๋ฐ ์‚ฌ์šฉ

Unix ๊ณ„์—ด ์šด์˜ ์ฒด์ œ์—์„œ ํ”„๋กœ๊ทธ๋žจ๊ณผ ์™ธ๋ถ€ ์„ธ๊ณ„ ๋ฐ ์šด์˜ ์ฒด์ œ์™€์˜ ํ†ต์‹ ์€ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด๋ผ๋Š” ์ž‘์€ ๊ธฐ๋Šฅ ์ง‘ํ•ฉ์„ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์ด๋Š” ๋””๋ฒ„๊น… ๋ชฉ์ ์œผ๋กœ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰๋˜๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ๊ฐ์‹œํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

Linux์—์„œ ํ”„๋กœ๊ทธ๋žจ์˜ "์นœ๋ฐ€ํ•œ ์ˆ˜๋ช…"์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ strace, ์ด๊ฒƒ์ด ์ด ๊ธฐ์‚ฌ์˜ ์ฃผ์ œ์ž…๋‹ˆ๋‹ค. ์ŠคํŒŒ์ด ์žฅ๋น„์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” ๊ฐ„๋žตํ•œ ์—ญ์‚ฌ๋ฅผ ๋™๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. strace ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋Ÿฌํ•œ ํ”„๋กœ๊ทธ๋žจ์˜ ์„ค๊ณ„์— ๋Œ€ํ•œ ์„ค๋ช….

๋‚ด์šฉ

์ข…์˜ ๊ธฐ์›

Unix์—์„œ ํ”„๋กœ๊ทธ๋žจ๊ณผ OS ์ปค๋„ ๊ฐ„์˜ ์ฃผ์š” ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ˜ธ์ถœ, ์‹œ์Šคํ…œ ํ˜ธ์ถœ), ํ”„๋กœ๊ทธ๋žจ๊ณผ ์™ธ๋ถ€ ์„ธ๊ณ„์˜ ์ƒํ˜ธ ์ž‘์šฉ์€ ์ด๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ตœ์ดˆ์˜ ๊ณต๊ฐœ ์œ ๋‹‰์Šค ๋ฒ„์ „์—์„œ๋Š” (๋ฒ„์ „ 6 ์œ ๋‹‰์Šค, 1975) ์‚ฌ์šฉ์ž ํ”„๋กœ์„ธ์Šค์˜ ๋™์ž‘์„ ์ถ”์ ํ•˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Bell Labs๋Š” ๋‹ค์Œ ๋ฒ„์ „(๋ฒ„์ „ 7 ์œ ๋‹‰์Šค, 1979)์€ ์ƒˆ๋กœ์šด ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ์ œ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค. ptrace.

ptrace๋Š” ์ฃผ๋กœ ๋Œ€ํ™”ํ˜• ๋””๋ฒ„๊ฑฐ๋ฅผ ์œ„ํ•ด ๊ฐœ๋ฐœ๋˜์—ˆ์œผ๋‚˜ 80๋…„๋Œ€ ๋ง(์ƒ์—…ํ™” ์‹œ๋Œ€)์— ์‹œ์Šคํ…œ V ๋ฆด๋ฆฌ์Šค 4) ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ข๊ฒŒ ์ดˆ์ ์„ ๋งž์ถ˜ ๋””๋ฒ„๊ฑฐ(์‹œ์Šคํ…œ ํ˜ธ์ถœ ์ถ”์ ๊ธฐ)๊ฐ€ ๋“ฑ์žฅํ•˜์—ฌ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒ˜์Œ์œผ๋กœ ๋™์ผํ•œ ๋ฒ„์ „์˜ strace๊ฐ€ ํ์‡„ํ˜• ์œ ํ‹ธ๋ฆฌํ‹ฐ์˜ ๋Œ€์•ˆ์œผ๋กœ Paul Cronenburg์— ์˜ํ•ด 1992๋…„ comp.sources.sun ๋ฉ”์ผ๋ง ๋ฆฌ์ŠคํŠธ์— ๊ฒŒ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. trace ํƒœ์–‘์—์„œ. ๋ณต์ œํ’ˆ๊ณผ ์›๋ณธ ๋ชจ๋‘ SunOS์šฉ์œผ๋กœ ์ œ์ž‘๋˜์—ˆ์ง€๋งŒ 1994๋…„์—๋Š” strace System V, Solaris ๋ฐ ์ ์  ๋” ์ธ๊ธฐ๋ฅผ ์–ป๊ณ  ์žˆ๋Š” Linux๋กœ ํฌํŒ…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ strace๋Š” Linux๋งŒ ์ง€์›ํ•˜๋ฉฐ ๋™์ผํ•œ Linux์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ptrace, ๋งŽ์€ ํ™•์žฅ์œผ๋กœ ์ž๋ž€๋‹ค.

ํ˜„๋Œ€์ ์ด๊ณ  ๋งค์šฐ ํ™œ๋™์ ์ธ ๊ด€๋ฆฌ์ž strace - ๋“œ๋ฏธํŠธ๋ฆฌ ๋ ˆ๋นˆ. ๊ทธ ๋•๋ถ„์— ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ์— ์˜ค๋ฅ˜ ์‚ฝ์ž…, ๊ด‘๋ฒ”์œ„ํ•œ ์•„ํ‚คํ…์ฒ˜ ์ง€์›, ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ๊ณผ ๊ฐ™์€ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ํš๋“ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์Šค์ฝ”ํŠธ. ๋น„๊ณต์‹ ์†Œ์‹ํ†ต์€ ๋Ÿฌ์‹œ์•„์–ด ๋‹จ์–ด "ํƒ€์กฐ"์™€ ์˜์–ด ๋‹จ์–ด "strace" ์‚ฌ์ด์˜ ์ž์Œ ๋•Œ๋ฌธ์— ์„ ํƒ์ด ํƒ€์กฐ์— ์ด๋ฅด๋ €๋‹ค๊ณ  ์ฃผ์žฅํ•ฉ๋‹ˆ๋‹ค.

Linux, FreeBSD, OpenBSD ๋ฐ ๊ธฐ์กด Unix์—์„œ์˜ ์˜ค๋žœ ์—ญ์‚ฌ์™€ ๊ตฌํ˜„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ptrace ์‹œ์Šคํ…œ ํ˜ธ์ถœ ๋ฐ ์ถ”์  ํ”„๋กœ๊ทธ๋žจ์ด POSIX์— ํฌํ•จ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ์ ๋„ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ Strace ์žฅ์น˜: Piglet Trace

"๋‹น์‹ ์€ ์ด๊ฒƒ์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค"(Dennis Ritchie, ๋ฒ„์ „ 6 Unix ์†Œ์Šค ์ฝ”๋“œ์˜ ์ฃผ์„)

์–ด๋ฆฐ ์‹œ์ ˆ๋ถ€ํ„ฐ ๋‚˜๋Š” ๋ธ”๋ž™ ๋ฐ•์Šค๋ฅผ ์ฐธ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ์žฅ๋‚œ๊ฐ์„ ๊ฐ€์ง€๊ณ  ๋†€์ง€๋Š” ์•Š์•˜์ง€๋งŒ ๊ทธ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค (์„ฑ์ธ๋“ค์€ "๋ถ€์„œ์กŒ๋‹ค"๋ผ๋Š” ๋‹จ์–ด๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์‚ฌ์•…ํ•œ ๋ฐฉ์–ธ์„ ๋ฏฟ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค). ์•„๋งˆ๋„ ์ด๊ฒƒ์ด ์ตœ์ดˆ์˜ Unix์˜ ๋น„๊ณต์‹ ๋ฌธํ™”์™€ ํ˜„๋Œ€ ์˜คํ”ˆ ์†Œ์Šค ์šด๋™์ด ๋‚˜์—๊ฒŒ ๊ทธํ† ๋ก ๊ฐ€๊นŒ์šด ์ด์œ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ๊ธ€์˜ ๋ชฉ์ ์„ ์œ„ํ•ด, ์ˆ˜์‹ญ ๋…„์— ๊ฑธ์ณ ์„ฑ์žฅํ•ด ์˜จ strace์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ถ„ํ•ดํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•ฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋…์ž๋“ค์—๊ฒŒ ๋น„๋ฐ€์ด ๋‚จ์•„ ์žˆ์–ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ์ถ”์  ํ”„๋กœ๊ทธ๋žจ์˜ ์ž‘๋™ ์›๋ฆฌ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์†Œํ˜• ์ถ”์  ํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํ”ผ๊ธ€๋ › ํŠธ๋ ˆ์ด์Šค (ptr). ํŠน๋ณ„ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด์ง€๋งŒ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ํ”„๋กœ๊ทธ๋žจ์˜ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค. ์ถœ๋ ฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

$ gcc examples/piglet-trace.c -o ptr
$ ptr echo test > /dev/null
BRK(12) -> 94744690540544
ACCESS(21) -> 18446744073709551614
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694657216512
CLOSE(3) -> 0
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
READ(0) -> 832
FSTAT(5) -> 0
MMAP(9) -> 140694657208320
MMAP(9) -> 140694650953728
MPROTECT(10) -> 0
MMAP(9) -> 140694655045632
MMAP(9) -> 140694655070208
CLOSE(3) -> 0
unknown(158) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MUNMAP(11) -> 0
BRK(12) -> 94744690540544
BRK(12) -> 94744690675712
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694646390784
CLOSE(3) -> 0
FSTAT(5) -> 0
IOCTL(16) -> 18446744073709551591
WRITE(1) -> 5
CLOSE(3) -> 0
CLOSE(3) -> 0
unknown(231)
Tracee terminated

Piglet Trace๋Š” ์•ฝ ์ˆ˜๋ฐฑ ๊ฐœ์˜ Linux ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค(์ฐธ์กฐ: ํ…Œ์ด๋ธ”) x86-64 ์•„ํ‚คํ…์ฒ˜์—์„œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ต์œก์šฉ์œผ๋กœ๋Š” ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

ํด๋ก ์˜ ์ž‘์—…์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Linux์˜ ๊ฒฝ์šฐ ๋””๋ฒ„๊ฑฐ์™€ ์ถ”์ ๊ธฐ๋Š” ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ptrace ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜์— ๋ช…๋ น ์‹๋ณ„์ž๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒƒ์€ PTRACE_TRACEME, PTRACE_SYSCALL ะธ PTRACE_GETREGS.

์ถ”์  ํ”„๋กœ๊ทธ๋žจ์€ ์ผ๋ฐ˜์ ์ธ Unix ์Šคํƒ€์ผ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. fork(2) ์ž์‹ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ณ , ์ฐจ๋ก€๋กœ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. exec(3) ์—ฐ๊ตฌ ์ค‘์ธ ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์œ ์ผํ•œ ๋ฏธ๋ฌ˜ํ•จ์€ ๋„์ „์ž…๋‹ˆ๋‹ค ptrace(PTRACE_TRACEME) ์ „์— exec: ์ž์‹ ํ”„๋กœ์„ธ์Šค๋Š” ๋ถ€๋ชจ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ž์‹ ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค.

pid_t child_pid = fork();
switch (child_pid) {
case -1:
    err(EXIT_FAILURE, "fork");
case 0:
    /* Child here */
    /* A traced mode has to be enabled. A parent will have to wait(2) for it
     * to happen. */
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    /* Replace itself with a program to be run. */
    execvp(argv[1], argv + 1);
    err(EXIT_FAILURE, "exec");
}

์ด์ œ ์ƒ์œ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. wait(2) ์ฆ‰, ์ถ”์  ๋ชจ๋“œ๋กœ ์ „ํ™˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.

/* Parent */

/* First we wait for the child to set the traced mode (see
 * ptrace(PTRACE_TRACEME) above) */
if (waitpid(child_pid, NULL, 0) == -1)
    err(EXIT_FAILURE, "traceme -> waitpid");

์ด์ œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฉฐ ๋ฌดํ•œ ๋ฃจํ”„์—์„œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์ถ”์ ์„ ์ง์ ‘ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋„์ „ ptrace(PTRACE_SYSCALL) ์ดํ›„์˜ ๋ณด์žฅ wait parent๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ์‹คํ–‰๋˜๊ธฐ ์ „์ด๋‚˜ ์™„๋ฃŒ๋œ ์งํ›„์— ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค. ๋‘ ํ˜ธ์ถœ ์‚ฌ์ด์—์„œ ํ˜ธ์ถœ์„ ๋Œ€์ฒด ํ˜ธ์ถœ๋กœ ๋Œ€์ฒดํ•˜๊ฑฐ๋‚˜ ์ธ์ˆ˜ ๋˜๋Š” ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋“ฑ ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ช…๋ น์„ ๋‘ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ptrace(PTRACE_GETREGS)๋ ˆ์ง€์Šคํ„ฐ ์ƒํƒœ๋ฅผ ์–ป์œผ๋ ค๋ฉด rax ํ˜ธ์ถœ ์ „(์‹œ์Šคํ…œ ํ˜ธ์ถœ ๋ฒˆํ˜ธ) ๋ฐ ์งํ›„(๋ฐ˜ํ™˜ ๊ฐ’).

์‹ค์ œ๋กœ ์ฃผ๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

/* A system call tracing loop, one interation per call. */
for (;;) {
    /* A non-portable structure defined for ptrace/GDB/strace usage mostly.
     * It allows to conveniently dump and access register state using
     * ptrace. */
    struct user_regs_struct registers;

    /* Enter syscall: continue execution until the next system call
     * beginning. Stop right before syscall.
     *
     * It's possible to change the system call number, system call
     * arguments, return value or even avoid executing the system call
     * completely. */
  if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
      err(EXIT_FAILURE, "enter_syscall");
  if (waitpid(child_pid, NULL, 0) == -1)
      err(EXIT_FAILURE, "enter_syscall -> waitpid");

  /* According to the x86-64 system call convention on Linux (see man 2
   * syscall) the number identifying a syscall should be put into the rax
   * general purpose register, with the rest of the arguments residing in
   * other general purpose registers (rdi,rsi, rdx, r10, r8, r9). */
  if (ptrace(PTRACE_GETREGS, child_pid, NULL, &registers) == -1)
      err(EXIT_FAILURE, "enter_syscall -> getregs");

  /* Note how orig_rax is used here. That's because on x86-64 rax is used
   * both for executing a syscall, and returning a value from it. To
   * differentiate between the cases both rax and orig_rax are updated on
   * syscall entry/exit, and only rax is updated on exit. */
  print_syscall_enter(registers.orig_rax);

  /* Exit syscall: execute of the syscall, and stop on system
   * call exit.
   *
   * More system call tinkering possible: change the return value, record
   * time it took to finish the system call, etc. */
  if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
      err(EXIT_FAILURE, "exit_syscall");
  if (waitpid(child_pid, NULL, 0) == -1)
      err(EXIT_FAILURE, "exit_syscall -> waitpid");

  /* Retrieve register state again as we want to inspect system call
   * return value. */
  if (ptrace(PTRACE_GETREGS, child_pid, NULL, &registers) == -1) {
      /* ESRCH is returned when a child terminates using a syscall and no
       * return value is possible, e.g. as a result of exit(2). */
      if (errno == ESRCH) {
          fprintf(stderr, "nTracee terminatedn");
          break;
      }
      err(EXIT_FAILURE, "exit_syscall -> getregs");
  }

  /* Done with this system call, let the next iteration handle the next
   * one */
  print_syscall_exit(registers.rax);
}

๊ทธ๊ฒƒ์ด ์ „์ฒด ์ถ”์ ์ž์ž…๋‹ˆ๋‹ค. ์ด์ œ ๋‹ค์Œ ํฌํŒ…์„ ์‹œ์ž‘ํ•  ์œ„์น˜๋ฅผ ์•Œ์•˜์Šต๋‹ˆ๋‹ค. DTrace ๋ฆฌ๋ˆ…์Šค์—์„œ.

๊ธฐ๋ณธ ์‚ฌํ•ญ: strace๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰

์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋กœ strace, ์•„๋งˆ๋„ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์ธ์šฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‹คํ–‰ ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. strace.

์ผ๋ฐ˜์ ์ธ ํ”„๋กœ๊ทธ๋žจ์˜ ๋์—†๋Š” ํ˜ธ์ถœ ๋ชฉ๋ก์„ ์กฐ์‚ฌํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ตœ์†Œ ํ”„๋กœ๊ทธ๋žจ ์ฃผ์œ„์— write:

int main(int argc, char *argv[])
{
    char str[] = "write me to stdoutn";
    /* write(2) is a simple wrapper around a syscall so it should be easy to
     * find in the syscall trace. */
    if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
        perror("write");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

ํ”„๋กœ๊ทธ๋žจ์„ ๋นŒ๋“œํ•˜๊ณ  ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ gcc examples/write-simple.c -o write-simple
$ ./write-simple
write me to stdout

๋งˆ์ง€๋ง‰์œผ๋กœ strace ์ œ์–ด ํ•˜์—์„œ ์‹คํ–‰ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ strace ./write-simple
pexecve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL)                               = 0x55ff5489e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=197410, ...}) = 0
mmap(NULL, 197410, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7a2a633000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1260342"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a631000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7a2a04c000
mprotect(0x7f7a2a233000, 2097152, PROT_NONE) = 0
mmap(0x7f7a2a433000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f7a2a433000
mmap(0x7f7a2a439000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a439000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7a2a6324c0) = 0
mprotect(0x7f7a2a433000, 16384, PROT_READ) = 0
mprotect(0x55ff52b52000, 4096, PROT_READ) = 0
mprotect(0x7f7a2a664000, 4096, PROT_READ) = 0
munmap(0x7f7a2a633000, 197410)          = 0
write(1, "write me to stdoutn", 20write me to stdout
)  = 20
exit_group(0)                           = ?

๋งค์šฐ "๋ง์ด ๋งŽ๊ณ " ๋ณ„๋กœ ๊ต์œก์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ ์ถœ๋ ฅ์ด ์ถœ๋ ฅ๊ณผ ํ˜ผํ•ฉ๋ฉ๋‹ˆ๋‹ค. strace ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์—๊ฒŒ ๊ด€์‹ฌ์ด ์—†๋Š” ํ’๋ถ€ํ•œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์‹œ์Šคํ…œ ํ˜ธ์ถœ ๋ชฉ๋ก์„ ์ธ์ˆ˜ ํŒŒ์ผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” -o ์Šค์œ„์น˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ๊ทธ๋žจ์˜ ํ‘œ์ค€ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ๊ณผ ์ถ”์  ์˜ค๋ฅ˜ ์ถœ๋ ฅ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"์ถ”๊ฐ€" ํ˜ธ์ถœ ๋ฌธ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธ์ถœ์—๋งŒ ๊ด€์‹ฌ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. write. ์—ด์‡  -e ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ํ•„ํ„ฐ๋งํ•  ํ‘œํ˜„์‹์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์กฐ๊ฑด ์˜ต์…˜์€ ๋‹น์—ฐํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. trace=*, ๊ด€์‹ฌ ์žˆ๋Š” ์ „ํ™”๋งŒ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ -o ะธ -e ์šฐ๋ฆฌ๋Š” ์–ป์„ ๊ฒƒ์ด๋‹ค:

$ strace -e trace=write -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20
)  = 20
+++ exited with 0 +++

๊ทธ๋ž˜์„œ, ์ฝ๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฝ์Šต๋‹ˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ๋ฐ ํ•ด์ œ์™€ ๊ด€๋ จ๋œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ ๋“ฑ์„ ์ œ๊ฑฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

$ strace -e trace=!brk,mmap,mprotect,munmap -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
execve("./write-simple", ["./write-simple"], 0x7ffe9972a498 /* 69 vars */) = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=124066, ...}) = 0
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1260342"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f00f0be74c0) = 0
write(1, "write me to stdoutn", 20)  = 20
exit_group(0)                           = ?
+++ exited with 0 +++

์ œ์™ธ๋œ ํ˜ธ์ถœ ๋ชฉ๋ก์—์„œ ์ด์Šค์ผ€์ดํ”„๋œ ๋Š๋‚Œํ‘œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ์ด๋Š” ๋ช…๋ น ์…ธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ป์งˆ).

๋‚ด ๋ฒ„์ „์˜ glibc์—์„œ๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. exit_group, ์ „ํ†ต์ ์ด์ง€ ์•Š์€ _exit. ์ด๊ฒƒ์ด ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์ž‘์—…์˜ ์–ด๋ ค์›€์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ž‘์—…ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ๊ณผ ์ง์ ‘์ ์ธ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ตฌํ˜„ ๋ฐ ํ”Œ๋žซํผ์— ๋”ฐ๋ผ ์ •๊ธฐ์ ์œผ๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌํ•ญ: ์ฆ‰์„์—์„œ ํ”„๋กœ์„ธ์Šค์— ์ฐธ์—ฌ

์ฒ˜์Œ์—๋Š” ๊ทธ๊ฒƒ์ด ๊ตฌ์ถ•๋œ ptrace ์‹œ์Šคํ…œ ํ˜ธ์ถœ strace, ํŠน์ˆ˜ ๋ชจ๋“œ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ œํ•œ์€ ๋ฒ„์ „ 6 Unix ์‹œ๋Œ€์—๋Š” ํ•ฉ๋ฆฌ์ ์œผ๋กœ ๋“ค๋ ธ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฆ˜์—๋Š” ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋” ์ด์ƒ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋•Œ๋กœ๋Š” ์ž‘๋™ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์˜ ๋ฌธ์ œ๋ฅผ ์กฐ์‚ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ „ํ˜•์ ์ธ ์˜ˆ๋Š” ํ•ธ๋“ค์—์„œ ์ฐจ๋‹จ๋˜๊ฑฐ๋‚˜ ์ž ์ž๋Š” ํ”„๋กœ์„ธ์Šค์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ํ˜„๋Œ€์ ์ธ strace ์ฆ‰์‹œ ํ”„๋กœ์„ธ์Šค์— ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ƒ‰๋™ ์˜ˆ ํ”„๋กœ๊ทธ๋žจ:

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    char str[] = "write men";

    write(STDOUT_FILENO, str, sizeof(str));

    /* Sleep indefinitely or until a signal arrives */
    pause();

    write(STDOUT_FILENO, str, sizeof(str));

    return EXIT_SUCCESS;
}

ํ”„๋กœ๊ทธ๋žจ์„ ๋นŒ๋“œํ•˜๊ณ  ๋™๊ฒฐ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

$ gcc examples/write-sleep.c -o write-sleep
$ ./write-sleep
./write-sleep
write me
^C
$

์ด์ œ ์ฐธ์—ฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ ./write-sleep &
[1] 15329
write me
$ strace -p 15329
strace: Process 15329 attached
pause(
^Cstrace: Process 15329 detached
 <detached ...>

ํ†ตํ™”๋กœ ์ธํ•ด ์ฐจ๋‹จ๋œ ํ”„๋กœ๊ทธ๋žจ pause. ๊ทธ๋…€๊ฐ€ ์‹ ํ˜ธ์— ์–ด๋–ป๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š”์ง€ ๋ด…์‹œ๋‹ค:

$ strace -o write-sleep.log -p 15329 &
strace: Process 15329 attached
$
$ kill -CONT 15329
$ cat write-sleep.log
pause()                                 = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause(
$
$ kill -TERM 15329
$ cat write-sleep.log
pause()                                 = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause()                                 = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
+++ killed by SIGTERM +++

์šฐ๋ฆฌ๋Š” ๊ณ ์ • ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•˜๊ณ  ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ฉ๋ฅ˜ํ–ˆ์Šต๋‹ˆ๋‹ค. strace. ๋‘ ๊ฐ€์ง€๊ฐ€ ๋ถ„๋ช…ํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ์ผ์‹œ ์ค‘์ง€ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์€ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†๋Š” ์‹ ํ˜ธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ , ๋” ํฅ๋ฏธ๋กญ๊ฒŒ๋„ strace๋Š” ์‹œ์Šคํ…œ ํ˜ธ์ถœ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋“ค์–ด์˜ค๋Š” ์‹ ํ˜ธ๋„ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ: ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค ์ถ”์ 

ํ˜ธ์ถœ์„ ํ†ตํ•œ ํ”„๋กœ์„ธ์Šค ์ž‘์—… fork - ๋ชจ๋“  ์œ ๋‹‰์Šค์˜ ๊ธฐ์ดˆ. ๊ฐ„๋‹จํ•œ "๋ฒˆ์‹"์˜ ์˜ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์„ธ์Šค ํŠธ๋ฆฌ์—์„œ strace๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ:

int main(int argc, char *argv[])
{
    pid_t parent_pid = getpid();
    pid_t child_pid = fork();
    if (child_pid == 0) {
        /* A child is born! */
        child_pid = getpid();

        /* In the end of the day printf is just a call to write(2). */
        printf("child (self=%d)n", child_pid);
        exit(EXIT_SUCCESS);
    }

    printf("parent (self=%d, child=%d)n", parent_pid, child_pid);

    wait(NULL);

    exit(EXIT_SUCCESS);
}

์—ฌ๊ธฐ์„œ ์›๋ž˜ ํ”„๋กœ์„ธ์Šค๋Š” ํ‘œ์ค€ ์ถœ๋ ฅ์— ์“ฐ๋Š” ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ gcc examples/fork-write.c -o fork-write
$ ./fork-write
parent (self=11274, child=11275)
child (self=11275)

๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ์œ„ ํ”„๋กœ์„ธ์Šค์˜ ์‹œ์Šคํ…œ ํ˜ธ์ถœ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ strace -e trace=write -ofork-write.log ./fork-write
child (self=22049)
parent (self=22048, child=22049)
$ cat fork-write.log
write(1, "parent (self=22048, child=22049)"..., 33) = 33
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22049, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

ํ”Œ๋ž˜๊ทธ๋Š” ์ „์ฒด ํ”„๋กœ์„ธ์Šค ํŠธ๋ฆฌ๋ฅผ ์ถ”์ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. -f, ์–ด๋Š strace ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค์˜ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๊ฐ ์ถœ๋ ฅ ๋ผ์ธ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. pid ์‹œ์Šคํ…œ ์ถœ๋ ฅ์„ ๋งŒ๋“œ๋Š” ํ”„๋กœ์„ธ์Šค:

$ strace -f -e trace=write -ofork-write.log ./fork-write
parent (self=22710, child=22711)
child (self=22711)
$ cat fork-write.log
22710 write(1, "parent (self=22710, child=22711)"..., 33) = 33
22711 write(1, "child (self=22711)n", 19) = 19
22711 +++ exited with 0 +++
22710 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22711, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
22710 +++ exited with 0 +++

์ด๋Ÿฌํ•œ ๋งฅ๋ฝ์—์„œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ ๊ทธ๋ฃน๋ณ„๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ strace -f -e trace=%process -ofork-write.log ./fork-write
parent (self=23610, child=23611)
child (self=23611)
$ cat fork-write.log
23610 execve("./fork-write", ["./fork-write"], 0x7fff696ff720 /* 63 vars */) = 0
23610 arch_prctl(ARCH_SET_FS, 0x7f3d03ba44c0) = 0
23610 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3d03ba4790) = 23611
23610 wait4(-1,  <unfinished ...>
23611 exit_group(0)                     = ?
23611 +++ exited with 0 +++
23610 <... wait4 resumed> NULL, 0, NULL) = 23611
23610 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23611, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
23610 exit_group(0)                     = ?
23610 +++ exited with 0 +++

๊ทธ๋Ÿฐ๋ฐ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์–ด๋–ค ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๊นŒ?

์˜ˆ: ํ•ธ๋“ค ๋Œ€์‹  ํŒŒ์ผ ๊ฒฝ๋กœ

ํŒŒ์ผ ์„ค๋ช…์ž๋ฅผ ์•„๋Š” ๊ฒƒ์€ ํ™•์‹คํžˆ ์œ ์šฉํ•˜์ง€๋งŒ ํ”„๋กœ๊ทธ๋žจ์ด ์•ก์„ธ์Šคํ•˜๋Š” ํŠน์ • ํŒŒ์ผ์˜ ์ด๋ฆ„๋„ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ํ”„๋กœ๊ทธ๋žจ ์ž„์‹œ ํŒŒ์ผ์— ๋‹ค์Œ ์ค„์„ ์”๋‹ˆ๋‹ค.

void do_write(int out_fd)
{
    char str[] = "write me to a filen";

    if (sizeof(str) != write(out_fd, str, sizeof(str))){
        perror("write");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[])
{
    char tmp_filename_template[] = "/tmp/output_fileXXXXXX";

    int out_fd = mkstemp(tmp_filename_template);
    if (out_fd == -1) {
        perror("mkstemp");
        exit(EXIT_FAILURE);
    }

    do_write(out_fd);

    return EXIT_SUCCESS;
}

์ผ๋ฐ˜ ํ†ตํ™” ์ค‘ strace ์‹œ์Šคํ…œ ํ˜ธ์ถœ์— ์ „๋‹ฌ๋œ ์„ค๋ช…์ž ๋ฒˆํ˜ธ์˜ ๊ฐ’์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

$ strace -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3, "write me to a filen", 20)  = 20
+++ exited with 0 +++

๊นƒ๋ฐœ๋กœ -y ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” ์„ค๋ช…์ž๊ฐ€ ํ•ด๋‹นํ•˜๋Š” ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

$ strace -y -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3</tmp/output_fileCf5MyW>, "write me to a filen", 20) = 20
+++ exited with 0 +++

์˜ˆ: ํŒŒ์ผ ์•ก์„ธ์Šค ์ถ”์ 

๋˜ ๋‹ค๋ฅธ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ: ํŠน์ • ํŒŒ์ผ๊ณผ ๊ด€๋ จ๋œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ํ”„๋กœ๊ทธ๋žจ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋œ ์ž„์˜์˜ ํŒŒ์ผ์— ํ•œ ์ค„์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

void do_write(int out_fd)
{
    char str[] = "write me to a filen";

    if (sizeof(str) != write(out_fd, str, sizeof(str))){
        perror("write");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[])
{
    /*
     * Path will be provided by the first program argument.
     *  */
    const char *path = argv[1];

    /*
     * Open an existing file for writing in append mode.
     *  */
    int out_fd = open(path, O_APPEND | O_WRONLY);
    if (out_fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    do_write(out_fd);

    return EXIT_SUCCESS;
}

๊ธฐ๋ณธ์ ์œผ๋กœ strace ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋งŽ์ด ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊นƒ๋ฐœ -P ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด strace๋Š” ์ง€์ •๋œ ํŒŒ์ผ์— ๋Œ€ํ•œ ํ˜ธ์ถœ๋งŒ ์ธ์‡„ํ•ฉ๋‹ˆ๋‹ค.

$ strace -y -P/tmp/test_file.log -o write-file.log ./write-file /tmp/test_file.log
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = 3</tmp/test_file.log>
write(3</tmp/test_file.log>, "write me to a filen", 20) = 20
+++ exited with 0 +++

์˜ˆ: ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ”„๋กœ๊ทธ๋žจ

๊ณต์ต ์‚ฌ์—… strace ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์ž‘์—… ์‹œ์—๋„ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ. ๋‹ค์Œ ํ”„๋กœ๊ทธ๋žจ์€ ๋‘ ์ŠคํŠธ๋ฆผ์˜ ํ‘œ์ค€ ์ถœ๋ ฅ์— ์”๋‹ˆ๋‹ค.

void *thread(void *arg)
{
    (void) arg;

    printf("Secondary thread: workingn");
    sleep(1);
    printf("Secondary thread: donen");

    return NULL;
}

int main(int argc, char *argv[])
{
    printf("Initial thread: launching a threadn");

    pthread_t thr;
    if (0 != pthread_create(&thr, NULL, thread, NULL)) {
        fprintf(stderr, "Initial thread: failed to create a thread");
        exit(EXIT_FAILURE);
    }

    printf("Initial thread: joining a threadn");
    if (0 != pthread_join(thr, NULL)) {
        fprintf(stderr, "Initial thread: failed to join a thread");
        exit(EXIT_FAILURE);
    };

    printf("Initial thread: done");

    exit(EXIT_SUCCESS);
}

๋‹น์—ฐํžˆ ๋ง์ปค์— ๋Œ€ํ•œ ํŠน๋ณ„ํ•œ ์ธ์‚ฌ๋ง์ธ -pthread ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํŒŒ์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

$ gcc examples/thread-write.c -pthread -o thread-write
$ ./thread-write
/thread-write
Initial thread: launching a thread
Initial thread: joining a thread
Secondary thread: working
Secondary thread: done
Initial thread: done
$

๊นƒ๋ฐœ -f, ์ผ๋ฐ˜ ํ”„๋กœ์„ธ์Šค์˜ ๊ฒฝ์šฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฐ ์ค„์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ํ”„๋กœ์„ธ์Šค์˜ pid๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๋‹น์—ฐํžˆ ์šฐ๋ฆฌ๋Š” POSIX Threads ํ‘œ์ค€ ๊ตฌํ˜„์ด๋ผ๋Š” ์˜๋ฏธ์—์„œ ์Šค๋ ˆ๋“œ ์‹๋ณ„์ž์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ Linux์˜ ์ž‘์—… ์Šค์ผ€์ค„๋Ÿฌ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ˆซ์ž์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ›„์ž์˜ ๊ด€์ ์—์„œ๋Š” ํ”„๋กœ์„ธ์Šค๋‚˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์‹œ์Šคํ…œ์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”์–ด ๊ฐ„์— ๋ถ„์‚ฐ๋˜์–ด์•ผ ํ•˜๋Š” ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…ํ•  ๋•Œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ๋„ˆ๋ฌด ๋งŽ์•„์ง‘๋‹ˆ๋‹ค.

$ strace -f -othread-write.log ./thread-write
$ wc -l thread-write.log
60 thread-write.log

ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์™€ ์‹œ์Šคํ…œ ํ˜ธ์ถœ๋กœ๋งŒ ์ž์‹ ์„ ์ œํ•œํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. write:

$ strace -f -e trace="%process,write" -othread-write.log ./thread-write
$ cat thread-write.log
18211 execve("./thread-write", ["./thread-write"], 0x7ffc6b8d58f0 /* 64 vars */) = 0
18211 arch_prctl(ARCH_SET_FS, 0x7f38ea3b7740) = 0
18211 write(1, "Initial thread: launching a thre"..., 35) = 35
18211 clone(child_stack=0x7f38e9ba2fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f38e9ba39d0, tls=0x7f38e9ba3700, child_tidptr=0x7f38e9ba39d0) = 18212
18211 write(1, "Initial thread: joining a thread"..., 33) = 33
18212 write(1, "Secondary thread: workingn", 26) = 26
18212 write(1, "Secondary thread: donen", 23) = 23
18212 exit(0)                           = ?
18212 +++ exited with 0 +++
18211 write(1, "Initial thread: done", 20) = 20
18211 exit_group(0)                     = ?
18211 +++ exited with 0 +++

๊ทธ๊ฑด ๊ทธ๋ ‡๊ณ , ์งˆ๋ฌธ. ์ƒˆ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์–ด๋–ค ์‹œ์Šคํ…œ ํ˜ธ์ถœ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๊นŒ? ์Šค๋ ˆ๋“œ ํ˜ธ์ถœ์€ ํ”„๋กœ์„ธ์Šค ํ˜ธ์ถœ๊ณผ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฆ…๋‹ˆ๊นŒ?

๋งˆ์Šคํ„ฐ ํด๋ž˜์Šค: ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์‹œ ํ”„๋กœ์„ธ์Šค ์Šคํƒ

์ตœ๊ทผ์— ๋“ฑ์žฅํ•œ ๊ฒƒ ์ค‘ ํ•˜๋‚˜ strace ๊ธฐ๋Šฅ - ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์‹œ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์Šคํƒ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์˜ˆ:

void do_write(void)
{
    char str[] = "write me to stdoutn";
    if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
        perror("write");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[])
{
    do_write();
    return EXIT_SUCCESS;
}

๋‹น์—ฐํžˆ ํ”„๋กœ๊ทธ๋žจ ์ถœ๋ ฅ์€ ๋งค์šฐ ๋ฐฉ๋Œ€ํ•ด์ง€๋ฉฐ ํ”Œ๋ž˜๊ทธ ์™ธ์—๋„ -k (ํ˜ธ์ถœ ์Šคํƒ ํ‘œ์‹œ) ์ด๋ฆ„๋ณ„๋กœ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์„ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

$ gcc examples/write-simple.c -o write-simple
$ strace -k -e trace=write -o write-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20)  = 20
 > /lib/x86_64-linux-gnu/libc-2.27.so(__write+0x14) [0x110154]
 > /home/vkazanov/projects-my/strace-post/write-simple(do_write+0x50) [0x78a]
 > /home/vkazanov/projects-my/strace-post/write-simple(main+0x14) [0x7d1]
 > /lib/x86_64-linux-gnu/libc-2.27.so(__libc_start_main+0xe7) [0x21b97]
 > /home/vkazanov/projects-my/strace-post/write-simple(_start+0x2a) [0x65a]
+++ exited with 0 +++

๋งˆ์Šคํ„ฐ ํด๋ž˜์Šค: ์˜ค๋ฅ˜ ์ฃผ์ž…

๊ทธ๋ฆฌ๊ณ  ๋˜ ํ•˜๋‚˜์˜ ์ƒˆ๋กญ๊ณ  ๋งค์šฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์€ ๋ฐ”๋กœ ์˜ค๋ฅ˜ ์ฃผ์ž…์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ํ”„๋กœ๊ทธ๋žจ, ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์— ๋‘ ์ค„์„ ์”๋‹ˆ๋‹ค.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void do_write(const char *str, ssize_t len)
{
    if (len != write(STDOUT_FILENO, str, (size_t)len)){
        perror("write");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    char str1[] = "write me 1n";
    do_write(str1, sizeof(str1));

    char str2[] = "write me 2n";
    do_write(str2, sizeof(str2));

    return EXIT_SUCCESS;
}

๋‘ ๊ฐ€์ง€ ์“ฐ๊ธฐ ํ˜ธ์ถœ์„ ๋ชจ๋‘ ์ถ”์ ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ gcc examples/write-twice.c -o write-twice
$ ./write-twice
write me 1
write me 2
$ strace -e trace=write -owrite-twice.log ./write-twice
write me 1
write me 2
$ cat write-twice.log
write(1, "write me 1n", 12)          = 12
write(1, "write me 2n", 12)          = 12
+++ exited with 0 +++

์ด์ œ ์šฐ๋ฆฌ๋Š” ํ‘œํ˜„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค inject์˜ค๋ฅ˜๋ฅผ ์‚ฝ์ž…ํ•˜๋ ค๋ฉด EBADF ๋ชจ๋“  ์“ฐ๊ธฐ ํ˜ธ์ถœ์—์„œ:

$ strace -e trace=write -e inject=write:error=EBADF -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12)          = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = -1 EBADF (Bad file descriptor) (INJECTED)
+++ exited with 1 +++

์–ด๋–ค ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”์ง€ ํฅ๋ฏธ๋กญ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋„์ „๋“ค write, perror ๋’ค์— ์ˆจ๊ฒจ์ง„ ํ˜ธ์ถœ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ํ˜ธ์ถœ์— ๋Œ€ํ•ด์„œ๋งŒ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค.

$ strace -e trace=write -e inject=write:error=EBADF:when=1 -owrite-twice.log ./write-twice
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12)          = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++

์•„๋‹ˆ๋ฉด ๋‘ ๋ฒˆ์งธ ๊ฒƒ:

$ strace -e trace=write -e inject=write:error=EBADF:when=2 -owrite-twice.log ./write-twice
write me 1
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12)          = 12
write(1, "write me 2n", 12)          = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++

์˜ค๋ฅ˜ ์œ ํ˜•์„ ์ง€์ •ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

$ strace -e trace=write -e fault=write:when=1 -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12)          = -1 ENOSYS (Function not implemented) (INJECTED)
write(3, "write: Function not implementedn", 32) = 32
+++ exited with 1 +++

๋‹ค๋ฅธ ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ํŒŒ์ผ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ "์ค‘๋‹จ"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ:

$ strace -y -P/tmp/test_file.log -e inject=file:error=ENOENT -o write-file.log ./write-file /tmp/test_file.log
open: No such file or directory
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = -1 ENOENT (No such file or directory) (INJECTED)
+++ exited with 1 +++

์˜ค๋ฅ˜ ์ฃผ์ž… ์™ธ์—๋„ ํ•˜๋‚˜๋Š” ์ˆ˜ ์ „ํ™”๋ฅผ ๊ฑธ๊ฑฐ๋‚˜ ์‹ ํ˜ธ๋ฅผ ๋ฐ›์„ ๋•Œ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์‚ฌํ›„

๊ณต์ต ์‚ฌ์—… strace - ๊ฐ„๋‹จํ•˜๊ณ  ์•ˆ์ •์ ์ธ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์™ธ์—๋„ ํ”„๋กœ๊ทธ๋žจ ์ž‘๋™ ๋ฐ ์šด์˜ ์ฒด์ œ์˜ ๋‹ค๋ฅธ ์ธก๋ฉด๋„ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋™์ ์œผ๋กœ ์—ฐ๊ฒฐ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ํ˜ธ์ถœ์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”์ , ์šด์˜ ์ฒด์ œ์˜ ์ž‘๋™์„ ์กฐ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œํƒญ ะธ ftrace, ํ”„๋กœ๊ทธ๋žจ ์„ฑ๋Šฅ์„ ์‹ฌ์ธต์ ์œผ๋กœ ์กฐ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ ํ•œ. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๊ทธ๊ฒƒ์€ strace - ๋‚ด ํ”„๋กœ๊ทธ๋žจ๊ณผ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ํ”„๋กœ๊ทธ๋žจ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ์˜ ์ฒซ ๋ฒˆ์งธ ๋ฐฉ์–ด์„ ์ด๋ฉฐ ์ผ์ฃผ์ผ์— ์ ์–ด๋„ ๋‘ ๋ฒˆ ์ด์ƒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ, Unix๋ฅผ ์ข‹์•„ํ•œ๋‹ค๋ฉด ์ด ์ฑ…์„ ์ฝ์–ด๋ณด์„ธ์š”. man 1 strace ๊ทธ๋ฆฌ๊ณ  ์ž์œ ๋กญ๊ฒŒ ํ”„๋กœ๊ทธ๋žจ์„ ์—ฟ๋ณด์„ธ์š”!

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€