ಲಿನಕ್ಸ್‌ನಲ್ಲಿ ಸ್ಟ್ರೇಸ್: ಇತಿಹಾಸ, ವಿನ್ಯಾಸ ಮತ್ತು ಬಳಕೆ

ಲಿನಕ್ಸ್‌ನಲ್ಲಿ ಸ್ಟ್ರೇಸ್: ಇತಿಹಾಸ, ವಿನ್ಯಾಸ ಮತ್ತು ಬಳಕೆ

ಯುನಿಕ್ಸ್ ತರಹದ ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಮ್‌ಗಳಲ್ಲಿ, ಹೊರಗಿನ ಪ್ರಪಂಚ ಮತ್ತು ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಮ್‌ನೊಂದಿಗೆ ಪ್ರೋಗ್ರಾಂನ ಸಂವಹನವು ಒಂದು ಸಣ್ಣ ಕಾರ್ಯಗಳ ಮೂಲಕ ಸಂಭವಿಸುತ್ತದೆ - ಸಿಸ್ಟಮ್ ಕರೆಗಳು. ಇದರರ್ಥ ಡೀಬಗ್ ಮಾಡುವ ಉದ್ದೇಶಗಳಿಗಾಗಿ ಪ್ರಕ್ರಿಯೆಗಳಿಂದ ಕಾರ್ಯಗತಗೊಳ್ಳುವ ಸಿಸ್ಟಮ್ ಕರೆಗಳ ಮೇಲೆ ಕಣ್ಣಿಡಲು ಇದು ಉಪಯುಕ್ತವಾಗಿದೆ.

ಲಿನಕ್ಸ್‌ನಲ್ಲಿನ ಕಾರ್ಯಕ್ರಮಗಳ "ನಿಕಟ ಜೀವನವನ್ನು" ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಲು ಉಪಯುಕ್ತತೆಯು ನಿಮಗೆ ಸಹಾಯ ಮಾಡುತ್ತದೆ strace, ಇದು ಈ ಲೇಖನದ ವಿಷಯವಾಗಿದೆ. ಪತ್ತೇದಾರಿ ಉಪಕರಣಗಳ ಬಳಕೆಯ ಉದಾಹರಣೆಗಳು ಸಂಕ್ಷಿಪ್ತ ಇತಿಹಾಸದೊಂದಿಗೆ ಇರುತ್ತವೆ strace ಮತ್ತು ಅಂತಹ ಕಾರ್ಯಕ್ರಮಗಳ ವಿನ್ಯಾಸದ ವಿವರಣೆ.

ಪರಿವಿಡಿ

ಜಾತಿಗಳ ಮೂಲ

ಯುನಿಕ್ಸ್‌ನಲ್ಲಿ ಪ್ರೋಗ್ರಾಂಗಳು ಮತ್ತು ಓಎಸ್ ಕರ್ನಲ್ ನಡುವಿನ ಮುಖ್ಯ ಇಂಟರ್ಫೇಸ್ ಸಿಸ್ಟಮ್ ಕರೆಗಳು. ಸಿಸ್ಟಮ್ ಕರೆಗಳು, ಸಿಸ್ಕಾಲ್ಗಳು), ಹೊರಗಿನ ಪ್ರಪಂಚದೊಂದಿಗೆ ಕಾರ್ಯಕ್ರಮಗಳ ಪರಸ್ಪರ ಕ್ರಿಯೆಯು ಅವುಗಳ ಮೂಲಕ ಪ್ರತ್ಯೇಕವಾಗಿ ಸಂಭವಿಸುತ್ತದೆ.

ಆದರೆ Unix ನ ಮೊದಲ ಸಾರ್ವಜನಿಕ ಆವೃತ್ತಿಯಲ್ಲಿ (ಆವೃತ್ತಿ 6 ಯುನಿಕ್ಸ್, 1975) ಬಳಕೆದಾರರ ಪ್ರಕ್ರಿಯೆಗಳ ನಡವಳಿಕೆಯನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಯಾವುದೇ ಅನುಕೂಲಕರ ಮಾರ್ಗಗಳಿಲ್ಲ. ಈ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸಲು, ಬೆಲ್ ಲ್ಯಾಬ್ಸ್ ಮುಂದಿನ ಆವೃತ್ತಿಗೆ ನವೀಕರಿಸುತ್ತದೆ (ಆವೃತ್ತಿ 7 ಯುನಿಕ್ಸ್, 1979) ಹೊಸ ಸಿಸ್ಟಮ್ ಕರೆಯನ್ನು ಪ್ರಸ್ತಾಪಿಸಿತು - ptrace.

ptrace ಅನ್ನು ಪ್ರಾಥಮಿಕವಾಗಿ ಸಂವಾದಾತ್ಮಕ ಡೀಬಗರ್‌ಗಳಿಗಾಗಿ ಅಭಿವೃದ್ಧಿಪಡಿಸಲಾಯಿತು, ಆದರೆ 80 ರ ದಶಕದ ಅಂತ್ಯದ ವೇಳೆಗೆ (ವಾಣಿಜ್ಯ ಯುಗದಲ್ಲಿ ಸಿಸ್ಟಮ್ ವಿ ಬಿಡುಗಡೆ 4) ಈ ಆಧಾರದ ಮೇಲೆ, ಸಂಕುಚಿತವಾಗಿ ಕೇಂದ್ರೀಕರಿಸಿದ ಡೀಬಗರ್‌ಗಳು-ಸಿಸ್ಟಮ್ ಕರೆ ಟ್ರೇಸರ್‌ಗಳು ಕಾಣಿಸಿಕೊಂಡವು ಮತ್ತು ವ್ಯಾಪಕವಾಗಿ ಬಳಸಲ್ಪಟ್ಟವು.

ಮೊದಲನೆಯದು ಸ್ಟ್ರೇಸ್‌ನ ಅದೇ ಆವೃತ್ತಿಯನ್ನು ಪಾಲ್ ಕ್ರೋನೆನ್‌ಬರ್ಗ್ ಅವರು 1992 ರಲ್ಲಿ comp.sources.sun ಮೇಲಿಂಗ್ ಪಟ್ಟಿಯಲ್ಲಿ ಮುಚ್ಚಿದ ಉಪಯುಕ್ತತೆಗೆ ಪರ್ಯಾಯವಾಗಿ ಪ್ರಕಟಿಸಿದರು trace ಸೂರ್ಯನಿಂದ. ಕ್ಲೋನ್ ಮತ್ತು ಮೂಲ ಎರಡೂ SunOS ಗಾಗಿ ಉದ್ದೇಶಿಸಲಾಗಿತ್ತು, ಆದರೆ 1994 ರ ಹೊತ್ತಿಗೆ strace ಸಿಸ್ಟಮ್ ವಿ, ಸೋಲಾರಿಸ್ ಮತ್ತು ಹೆಚ್ಚು ಜನಪ್ರಿಯವಾಗಿರುವ ಲಿನಕ್ಸ್‌ಗೆ ಪೋರ್ಟ್ ಮಾಡಲಾಗಿದೆ.

ಇಂದು strace ಲಿನಕ್ಸ್ ಅನ್ನು ಮಾತ್ರ ಬೆಂಬಲಿಸುತ್ತದೆ ಮತ್ತು ಅದನ್ನೇ ಅವಲಂಬಿಸಿದೆ ptrace, ಅನೇಕ ವಿಸ್ತರಣೆಗಳೊಂದಿಗೆ ಮಿತಿಮೀರಿ ಬೆಳೆದಿದೆ.

ಆಧುನಿಕ (ಮತ್ತು ಅತ್ಯಂತ ಸಕ್ರಿಯ) ನಿರ್ವಾಹಕ strace - ಡಿಮಿಟ್ರಿ ಲೆವಿನ್. ಅವರಿಗೆ ಧನ್ಯವಾದಗಳು, ಉಪಯುಕ್ತತೆಯು ಸಿಸ್ಟಮ್ ಕರೆಗಳಿಗೆ ದೋಷ ಇಂಜೆಕ್ಷನ್, ವ್ಯಾಪಕ ಶ್ರೇಣಿಯ ಆರ್ಕಿಟೆಕ್ಚರ್‌ಗಳಿಗೆ ಬೆಂಬಲ ಮತ್ತು ಮುಖ್ಯವಾಗಿ, ಸುಧಾರಿತ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪಡೆದುಕೊಂಡಿದೆ. ಮ್ಯಾಸ್ಕಾಟ್. ರಷ್ಯಾದ ಪದ "ಆಸ್ಟ್ರಿಚ್" ಮತ್ತು ಇಂಗ್ಲಿಷ್ ಪದ "ಸ್ಟ್ರೇಸ್" ನಡುವಿನ ವ್ಯಂಜನದಿಂದಾಗಿ ಆಯ್ಕೆಯು ಆಸ್ಟ್ರಿಚ್ ಮೇಲೆ ಬಿದ್ದಿದೆ ಎಂದು ಅನಧಿಕೃತ ಮೂಲಗಳು ಹೇಳುತ್ತವೆ.

ಲಿನಕ್ಸ್, ಫ್ರೀಬಿಎಸ್‌ಡಿ, ಓಪನ್‌ಬಿಎಸ್‌ಡಿ ಮತ್ತು ಸಾಂಪ್ರದಾಯಿಕ ಯುನಿಕ್ಸ್‌ನಲ್ಲಿ ಸುದೀರ್ಘ ಇತಿಹಾಸ ಮತ್ತು ಅಳವಡಿಕೆಯ ಹೊರತಾಗಿಯೂ, ptrace ಸಿಸ್ಟಮ್ ಕರೆ ಮತ್ತು ಟ್ರೇಸರ್‌ಗಳನ್ನು POSIX ನಲ್ಲಿ ಎಂದಿಗೂ ಸೇರಿಸಲಾಗಿಲ್ಲ ಎಂಬುದು ಸಹ ಮುಖ್ಯವಾಗಿದೆ.

ಸಂಕ್ಷಿಪ್ತವಾಗಿ ಸ್ಟ್ರೇಸ್ ಸಾಧನ: ಹಂದಿಮರಿ ಟ್ರೇಸ್

"ನೀವು ಇದನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವ ನಿರೀಕ್ಷೆಯಿಲ್ಲ" (ಡೆನ್ನಿಸ್ ರಿಚ್ಚಿ, ಆವೃತ್ತಿ 6 ಯುನಿಕ್ಸ್ ಮೂಲ ಕೋಡ್‌ನಲ್ಲಿ ಕಾಮೆಂಟ್)

ಬಾಲ್ಯದಿಂದಲೂ, ನಾನು ಕಪ್ಪು ಪೆಟ್ಟಿಗೆಗಳನ್ನು ನಿಲ್ಲಲು ಸಾಧ್ಯವಿಲ್ಲ: ನಾನು ಆಟಿಕೆಗಳೊಂದಿಗೆ ಆಡಲಿಲ್ಲ, ಆದರೆ ಅವರ ರಚನೆಯನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿದೆ (ವಯಸ್ಕರು "ಮುರಿದ" ಪದವನ್ನು ಬಳಸಿದರು, ಆದರೆ ದುಷ್ಟ ನಾಲಿಗೆಯನ್ನು ನಂಬುವುದಿಲ್ಲ). ಬಹುಶಃ ಇದಕ್ಕಾಗಿಯೇ ಮೊದಲ Unix ನ ಅನೌಪಚಾರಿಕ ಸಂಸ್ಕೃತಿ ಮತ್ತು ಆಧುನಿಕ ಮುಕ್ತ-ಮೂಲ ಚಳುವಳಿ ನನಗೆ ತುಂಬಾ ಹತ್ತಿರವಾಗಿದೆ.

ಈ ಲೇಖನದ ಉದ್ದೇಶಗಳಿಗಾಗಿ, ಸ್ಟ್ರೇಸ್ನ ಮೂಲ ಕೋಡ್ ಅನ್ನು ಡಿಸ್ಅಸೆಂಬಲ್ ಮಾಡುವುದು ಅಸಮಂಜಸವಾಗಿದೆ, ಇದು ದಶಕಗಳಿಂದ ಬೆಳೆದಿದೆ. ಆದರೆ ಓದುಗರಿಗೆ ಯಾವುದೇ ರಹಸ್ಯಗಳು ಉಳಿಯಬಾರದು. ಆದ್ದರಿಂದ, ಅಂತಹ ಸ್ಟ್ರೇಸ್ ಪ್ರೋಗ್ರಾಂಗಳ ಕಾರ್ಯಾಚರಣೆಯ ತತ್ವವನ್ನು ತೋರಿಸಲು, ನಾನು ಚಿಕಣಿ ಟ್ರೇಸರ್ಗಾಗಿ ಕೋಡ್ ಅನ್ನು ಒದಗಿಸುತ್ತೇನೆ - ಹಂದಿಮರಿ ಟ್ರೇಸ್ (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

ಹಂದಿಮರಿ ಟ್ರೇಸ್ ನೂರಾರು ಲಿನಕ್ಸ್ ಸಿಸ್ಟಮ್ ಕರೆಗಳನ್ನು ಗುರುತಿಸುತ್ತದೆ (ನೋಡಿ. ಟೇಬಲ್) ಮತ್ತು x86-64 ಆರ್ಕಿಟೆಕ್ಚರ್‌ನಲ್ಲಿ ಮಾತ್ರ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ. ಶೈಕ್ಷಣಿಕ ಉದ್ದೇಶಗಳಿಗಾಗಿ ಇದು ಸಾಕಾಗುತ್ತದೆ.

ನಮ್ಮ ತದ್ರೂಪಿಯ ಕೆಲಸವನ್ನು ನೋಡೋಣ. ಲಿನಕ್ಸ್‌ನ ಸಂದರ್ಭದಲ್ಲಿ, ಡೀಬಗರ್‌ಗಳು ಮತ್ತು ಟ್ರೇಸರ್‌ಗಳು ಮೇಲೆ ತಿಳಿಸಿದಂತೆ, ptrace ಸಿಸ್ಟಮ್ ಕರೆಯನ್ನು ಬಳಸುತ್ತವೆ. ಮೊದಲ ಆರ್ಗ್ಯುಮೆಂಟ್‌ನಲ್ಲಿ ಕಮಾಂಡ್ ಐಡೆಂಟಿಫೈಯರ್‌ಗಳನ್ನು ರವಾನಿಸುವ ಮೂಲಕ ಇದು ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ, ಅದರಲ್ಲಿ ನಮಗೆ ಮಾತ್ರ ಅಗತ್ಯವಿದೆ PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

ಟ್ರೇಸರ್ ಸಾಮಾನ್ಯ ಯುನಿಕ್ಸ್ ಶೈಲಿಯಲ್ಲಿ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ: 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 ಸಿಸ್ಟಮ್ ಕರೆಯನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುವ ಮೊದಲು ಅಥವಾ ಅದು ಪೂರ್ಣಗೊಂಡ ತಕ್ಷಣ ಪೋಷಕರು ಪೂರ್ಣಗೊಳಿಸುತ್ತಾರೆ. ಎರಡು ಕರೆಗಳ ನಡುವೆ ನೀವು ಯಾವುದೇ ಕ್ರಿಯೆಗಳನ್ನು ಮಾಡಬಹುದು: ಕರೆಯನ್ನು ಪರ್ಯಾಯವಾಗಿ ಬದಲಾಯಿಸಿ, ಆರ್ಗ್ಯುಮೆಂಟ್‌ಗಳು ಅಥವಾ ರಿಟರ್ನ್ ಮೌಲ್ಯವನ್ನು ಬದಲಾಯಿಸಿ.

ನಾವು ಆಜ್ಞೆಯನ್ನು ಎರಡು ಬಾರಿ ಕರೆಯಬೇಕಾಗಿದೆ 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);
}

ಅದು ಸಂಪೂರ್ಣ ಟ್ರೇಸರ್ ಆಗಿದೆ. ಮುಂದಿನ ಪೋರ್ಟಿಂಗ್ ಅನ್ನು ಎಲ್ಲಿ ಪ್ರಾರಂಭಿಸಬೇಕು ಎಂದು ಈಗ ನಿಮಗೆ ತಿಳಿದಿದೆ ಡಿಟ್ರೇಸ್ Linux ನಲ್ಲಿ.

ಮೂಲಭೂತ ಅಂಶಗಳು: ಪ್ರೋಗ್ರಾಂ ಚಾಲನೆಯಲ್ಲಿರುವ ಸ್ಟ್ರೇಸ್ ಅನ್ನು ಚಾಲನೆ ಮಾಡುವುದು

ಮೊದಲ ಬಳಕೆಯ ಪ್ರಕರಣವಾಗಿ 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 ./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 ಯುನಿಕ್ಸ್‌ನ ದಿನಗಳಲ್ಲಿ ಈ ಮಿತಿಯು ಸಮಂಜಸವೆಂದು ತೋರುತ್ತದೆ. ಇತ್ತೀಚಿನ ದಿನಗಳಲ್ಲಿ, ಇದು ಇನ್ನು ಮುಂದೆ ಸಾಕಾಗುವುದಿಲ್ಲ: ಕೆಲವೊಮ್ಮೆ ನೀವು ಕೆಲಸ ಮಾಡುವ ಕಾರ್ಯಕ್ರಮದ ಸಮಸ್ಯೆಗಳನ್ನು ತನಿಖೆ ಮಾಡಬೇಕಾಗುತ್ತದೆ. ಒಂದು ವಿಶಿಷ್ಟ ಉದಾಹರಣೆಯೆಂದರೆ ಹ್ಯಾಂಡಲ್ ಅಥವಾ ಸ್ಲೀಪಿಂಗ್‌ನಲ್ಲಿ ನಿರ್ಬಂಧಿಸಲಾದ ಪ್ರಕ್ರಿಯೆ. ಆದ್ದರಿಂದ ಆಧುನಿಕ 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. ಎರಡು ವಿಷಯಗಳು ಸ್ಪಷ್ಟವಾಯಿತು: ವಿರಾಮ ಸಿಸ್ಟಂ ಕರೆ ಹ್ಯಾಂಡ್ಲರ್‌ಗಳಿಲ್ಲದೆ ಸಂಕೇತಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸುತ್ತದೆ ಮತ್ತು ಹೆಚ್ಚು ಆಸಕ್ತಿಕರವಾಗಿ, ಸ್ಟ್ರೇಸ್ ಮಾನಿಟರ್ ಸಿಸ್ಟಮ್ ಕರೆಗಳನ್ನು ಮಾತ್ರವಲ್ಲದೆ ಒಳಬರುವ ಸಂಕೇತಗಳನ್ನೂ ಸಹ ಮಾಡುತ್ತದೆ.

ಉದಾಹರಣೆ: ಮಕ್ಕಳ ಪ್ರಕ್ರಿಯೆಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡುವುದು

ಕರೆ ಮೂಲಕ ಪ್ರಕ್ರಿಯೆಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವುದು fork - ಎಲ್ಲಾ ಯುನಿಕ್ಸ್‌ಗಳ ಆಧಾರ. ಸರಳವಾದ "ಸಂತಾನೋತ್ಪತ್ತಿ" ಯ ಉದಾಹರಣೆಯನ್ನು ಬಳಸಿಕೊಂಡು ಪ್ರಕ್ರಿಯೆಯ ಮರದೊಂದಿಗೆ ಸ್ಟ್ರೇಸ್ ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ನೋಡೋಣ. ಕಾರ್ಯಕ್ರಮಗಳು:

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 -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, ನಿಯಮಿತ ಪ್ರಕ್ರಿಯೆಗಳಂತೆ, ಪ್ರತಿ ಸಾಲಿನ ಪ್ರಾರಂಭಕ್ಕೆ ಪ್ರಕ್ರಿಯೆಯ ಪಿಡ್ ಅನ್ನು ಸೇರಿಸುತ್ತದೆ.

ಸ್ವಾಭಾವಿಕವಾಗಿ, ನಾವು POSIX ಥ್ರೆಡ್‌ಗಳ ಮಾನದಂಡದ ಅನುಷ್ಠಾನದ ಅರ್ಥದಲ್ಲಿ ಥ್ರೆಡ್ ಐಡೆಂಟಿಫೈಯರ್ ಬಗ್ಗೆ ಮಾತನಾಡುತ್ತಿಲ್ಲ, ಆದರೆ ಲಿನಕ್ಸ್‌ನಲ್ಲಿ ಟಾಸ್ಕ್ ಶೆಡ್ಯೂಲರ್ ಬಳಸುವ ಸಂಖ್ಯೆಯ ಬಗ್ಗೆ. ನಂತರದ ದೃಷ್ಟಿಕೋನದಿಂದ, ಯಾವುದೇ ಪ್ರಕ್ರಿಯೆಗಳು ಅಥವಾ ಎಳೆಗಳಿಲ್ಲ - ಯಂತ್ರದ ಲಭ್ಯವಿರುವ ಕೋರ್ಗಳಲ್ಲಿ ವಿತರಿಸಬೇಕಾದ ಕಾರ್ಯಗಳಿವೆ.

ಬಹು ಥ್ರೆಡ್‌ಗಳಲ್ಲಿ ಕೆಲಸ ಮಾಡುವಾಗ, ಸಿಸ್ಟಮ್ ಕರೆಗಳು ಹಲವಾರು ಆಗುತ್ತವೆ:

$ 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, ಅಪರಾಧದ ಹಿಂದೆ ಅಡಗಿರುವ ಕರೆ ಸೇರಿದಂತೆ. ಮೊದಲ ಕರೆಗಳಿಗೆ ದೋಷವನ್ನು ಹಿಂತಿರುಗಿಸಲು ಮಾತ್ರ ಇದು ಅರ್ಥಪೂರ್ಣವಾಗಿದೆ:

$ 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 - ಸರಳ ಮತ್ತು ವಿಶ್ವಾಸಾರ್ಹ ಸಾಧನ. ಆದರೆ ಸಿಸ್ಟಮ್ ಕರೆಗಳ ಜೊತೆಗೆ, ಕಾರ್ಯಕ್ರಮಗಳ ಕಾರ್ಯಾಚರಣೆಯ ಇತರ ಅಂಶಗಳು ಮತ್ತು ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಮ್ ಅನ್ನು ಡೀಬಗ್ ಮಾಡಬಹುದು. ಉದಾಹರಣೆಗೆ, ಇದು ಕ್ರಿಯಾತ್ಮಕವಾಗಿ ಲಿಂಕ್ ಮಾಡಲಾದ ಲೈಬ್ರರಿಗಳಿಗೆ ಕರೆಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಬಹುದು. ಟ್ರೇಸ್, ಅವರು ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಂನ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ನೋಡಬಹುದು SystemTap и ಫ್ರೇಸ್, ಮತ್ತು ಪ್ರೋಗ್ರಾಂ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಆಳವಾಗಿ ತನಿಖೆ ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ ಪರಿಪೂರ್ಣ. ಅದೇನೇ ಇದ್ದರೂ, ಅದು strace - ನನ್ನ ಸ್ವಂತ ಮತ್ತು ಇತರ ಜನರ ಕಾರ್ಯಕ್ರಮಗಳೊಂದಿಗೆ ಸಮಸ್ಯೆಗಳ ಸಂದರ್ಭದಲ್ಲಿ ರಕ್ಷಣೆಯ ಮೊದಲ ಸಾಲು, ಮತ್ತು ನಾನು ಅದನ್ನು ವಾರಕ್ಕೆ ಕನಿಷ್ಠ ಒಂದೆರಡು ಬಾರಿ ಬಳಸುತ್ತೇನೆ.

ಸಂಕ್ಷಿಪ್ತವಾಗಿ, ನೀವು ಯುನಿಕ್ಸ್ ಅನ್ನು ಪ್ರೀತಿಸುತ್ತಿದ್ದರೆ, ಓದಿ man 1 strace ಮತ್ತು ನಿಮ್ಮ ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ನೋಡಲು ಮುಕ್ತವಾಗಿರಿ!

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ