Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic tracing without compiler support #281

Closed
ParkHanbum opened this issue Dec 6, 2017 · 113 comments
Closed

dynamic tracing without compiler support #281

ParkHanbum opened this issue Dec 6, 2017 · 113 comments

Comments

@ParkHanbum
Copy link
Contributor

ParkHanbum commented Dec 6, 2017

currently uftrace use instrumentation feature that supported by compiler. but this dependency cause another dependency, for example a user who want use uftrace need knowledge of build system by reason of must rebuild binary with special compile option for using uftrace.

so, I think if we can support the instrumentation feature without any dependency, that is great useful.

in my opinion, there is two approach for that.

first, maybe we can use dynamic instrumentation engine like dynamoRIO. maybe you guys know about dynamic instrumentation already, so I'm summary this to 3 line.

  • high cost.
  • very utility.
  • maybe easier then second.

second, build binary rewriting engine for inject instrumentation point(static or dynamic).

  1. analysis CFG
  2. inject instrumentation point at and before of call instruction.
  3. adjusting binary layout.

how do you think?

@ParkHanbum ParkHanbum changed the title the instrumentation feature for independent from compiler. the support instrumentation feature for independent from compiler. Dec 6, 2017
@namhyung
Copy link
Owner

namhyung commented Dec 6, 2017

@ParkHanbum thanks for suggestion. I need more survey on existing dynamic instrumentation engines, but I'm also considering the second option. In our case, we don't need to do full CFG analysis since only a few of prologue instructions will be instrumented.

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Dec 7, 2017

@namhyung
would you explain about what is the prologue? when we can get symbol information from binary, then we can know about function start address. but without symbol or debugging information, we can't assume that. gcc does not make stack frame point always, right?

so, I feel needs about CFG... did you have approaches for it?

@namhyung namhyung changed the title the support instrumentation feature for independent from compiler. dynamic tracing without compiler support Dec 9, 2017
@namhyung
Copy link
Owner

namhyung commented Dec 9, 2017

A prologue is a sequence of instructions to setup necessary data at the beginning of a function. Please see the wikipedia page.

@ParkHanbum
Copy link
Contributor Author

@namhyung I know but as I say it does not made always. is it right?

@namhyung
Copy link
Owner

Well, uftrace only needs first few instructions to put a call instruction anyway.

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Dec 12, 2017

@namhyung

if we can assume that start point of function, maybe we can instrument at the correct position. but without compiler supporting, there is some variable.

in my opinion, it is hard to find the start of function perfectly. because prologue of function is not necessary always, if so we can not assume the start point of function. and The situation I suppose is without any debugging symbol. it makes hard to assume that, too.

for that reason, maybe pre/post call instruction is better point for instrumentation.

@namhyung
Copy link
Owner

The situation I suppose is without any debugging symbol.

Did you mean the binary doesn't have symbol table? I'll just give up then.

for that reason, maybe pre/post call instruction is better point for instrumentation.

What is pre/post call instruction?

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Dec 17, 2017

@namhyung
yes, that is what I'm say. without any supporting from compiler.
pre/post call instruction = before and after call instruction like below.

call mcount-precall@plt    -- parse argument and function call
call blahblah
call mcount-postcall@plt   -- parse result

I think that is the better point to instrumentation when debugging symbols does not exist.
if you think this is useful, I want to try this.
how do you think about this?

@namhyung
Copy link
Owner

I don't understand what you try to do. Why not adding calls to mcount at the function prologue? Adding instrumentation code in the middle of body would be difficult and unsafe IMO.

@ParkHanbum
Copy link
Contributor Author

@namhyung
I want support any binary without any addtional information, like debugging symbols. that is my purpose. I think it is great useful for various work. for example, it is useful for find bug from 3rd-party library(include commercial).

for now, I will try inject instrumentation point at prologue of function like as you say, first. if anyone have not try this now.

@namhyung
Copy link
Owner

What do you mean by debugging symbols - debug info? or symbol table?

Anyway, it'd be really really great if we could trace binary without recompilation. I plan to try it but I have things to do before that. Please feel free to share your design and implementation as early as possible though.

@ParkHanbum
Copy link
Contributor Author

maybe this repository would helpful for this issue.
https://github.com/leviathansecurity/ftrace.git

I'll comment mechanism of ftrace, as soon as possible.

@namhyung
Copy link
Owner

namhyung commented Jan 5, 2018

At a glance, it seems to use ptrace which I'd like to avoid if possible.

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Jan 6, 2018

@namhyung
what reason to avoid using ptrace?
even if not use ptrace, dynamic trace need to implement same feature.
first of all, we need to stop process execution.

but if need that, maybe there is a way for reduce the part of ptrace.
I'll research for that.

@namhyung
Copy link
Owner

namhyung commented Jan 15, 2018

Sorry for late reply. I want to avoid ptrace for a performance reason and don't want to stop process execution as well. It could execute some more instructions but should not be stopped IMO.

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Jan 16, 2018

@namhyung
I'm trying to inject libmcount into a running process using ptrace. but this approach need to stop process and inject some instruction to load libmcount by using dlopen.

I think this way is best. if you have a better idea or approach, please let me know.

@namhyung
Copy link
Owner

Ah right, for the injection, it needs ptrace. I meant that it should not use ptrace for tracing after the injection.

@ParkHanbum
Copy link
Contributor Author

I was success to inject libmcount module to running process at x86 64bit. just a prototype as proof a concept. still a long way to go. but for notice, I have left this comment.

m@ubuntu:~/test/uftrace$ ./uftrace dynamic -v --pid=14356
uftrace: FIND EXECUTABLE FILE PATH : /home/m/test/uftrace/sample-target
uftrace: removing uftrace.data.old directory
uftrace: skipping perf event due to error: Permission denied
uftrace: please check /proc/sys/kernel/perf_event_paranoid
uftrace: creating 1 thread(s) for recording
setup start
LOG setup environ
uftrace: using libmcount-dynamic.so library for tracing
setup end
uftrace: ENVIRONMENT READY. NOw INJECTING...
Library file path : /home/m/test/uftrace/libtrigger.so
continue
__libc_dlopen_mode
continue
"libtrigger.so" successfully injected
continue
DONE
m@ubuntu:~/test/uftrace$ ./uftrace replay
# DURATION     TID     FUNCTION
  37.034 us [ 14356] | printf();
   1.001  s [ 14356] | nanosleep();
  18.003 us [ 14356] | printf();
   1.003  s [ 14356] | nanosleep();
  10.341 us [ 14356] | printf();
   1.003  s [ 14356] | nanosleep();
   8.520 us [ 14356] | printf();
   1.000  s [ 14356] | nanosleep();
   9.116 us [ 14356] | printf();
   1.000  s [ 14356] | nanosleep();
   8.592 us [ 14356] | printf();
   1.004  s [ 14356] | nanosleep();
   9.107 us [ 14356] | printf();

@namhyung
Copy link
Owner

@ParkHanbum Cool!!

@honggyukim
Copy link
Collaborator

@ParkHanbum Looks very cool!

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Apr 6, 2018

we had tried dynamic tracing without compiler support feature by using breakpoint and sighandler.
you can find the code here : https://github.com/ParkHanbum/uftrace/tree/dynamic_trace_inject_done

to explain :

inject code to call "__libc_dlopen_mode" function that exported at libc.so
1-1. load shared object "libtrigger.so"
"libtrigger.so" just load "libmcount-dynamic.so" that similar with "libmcount.so"
(It could not load directly because of the difference in "_libc_dlopen_mode" and "dlopen".)
"libmcount-dynamic.so" set the breakpoint at start of local functions.
"libmcount-dynamic.so" set the signal for catching SIGTRAP signal that occured by breakpoint.

in runtime.

program trapped when reach breakpoint.
"libmcount-dynamic.so" catch the SIGTRAP signal.
do the mcount stub(call mcount_entry and change return address).
restore original instruction at breakpoint located.

that result is here :

m@ubuntu:~/test/uftrace$ ./uftrace replay
# DURATION     TID     FUNCTION
   1.797 us [ 30090] | gettimeofday();
  15.251 us [ 30090] | printf();
   4.787 us [ 30090] | printf();
            [ 30090] | _func6() {
   1.000  s [ 30090] |   nanosleep();
   0.879 us [ 30090] |   gettimeofday();
  51.276 us [ 30090] |   printf();
   1.000  s [ 30090] | } /* _func6 */
   5.943 us [ 30090] | printf();
            [ 30090] | _func6() {
   1.000  s [ 30090] |   nanosleep();
   0.892 us [ 30090] |   gettimeofday();
  51.714 us [ 30090] |   printf();
   1.000  s [ 30090] | } /* _func6 */
   5.322 us [ 30090] | printf();
            [ 30090] | _func6() {
   1.000  s [ 30090] |   nanosleep();
   0.640 us [ 30090] |   gettimeofday();
  39.255 us [ 30090] |   printf();
   1.000  s [ 30090] | } /* _func6 */
...(skip).......

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented Apr 17, 2018

UPDATE

I was make simple ppt to explain dynamic instrumation.
ppt is here : LINK

CODE

link : https://github.com/ParkHanbum/uftrace/commits/dynamic_trace_2

RESTRICTION

For dynamic instrumentation, we need 6 bytes of space to patch from the function prologue.
if there is an instruction that to reference relative address or something, we can't handle it now.

@namhyung
Copy link
Owner

link is broken

@namhyung
Copy link
Owner

I revived the Hanbum's work (#588) and pushed to review/dynamic-trace-v1.

I tried to keep original patches with small coding-style changes and added my changed on top. Notable change is using separate mmap area for saved (original) instructions. It works for the simplest cases on my machine and I'd like to improve it incrementally.

But strangely, it sometimes runs very slow. Also I didn't test it much so please play with it together. :)

@namhyung
Copy link
Owner

With below change, it can trace more functions. Rational is to allow memory access unless it's PC-relative.

diff --git a/arch/x86_64/mcount-dynamic.c b/arch/x86_64/mcount-dynamic.c
index baab770c..23219e22 100644
--- a/arch/x86_64/mcount-dynamic.c
+++ b/arch/x86_64/mcount-dynamic.c
@@ -484,25 +484,12 @@ static int check_instrumentable(csh ud, cs_insn *ins)
                        status = CODE_PATCH_OK;
                        break;
                case X86_OP_MEM:
-                       // temporary till discover possibility of x86 instructions.
-                       status = CODE_PATCH_NO;
-
-                       if (op->mem.segment != X86_REG_INVALID)
-                               pr_dbg2("\t\t\toperands[%u].mem.segment: REG = %s\n",
-                                       i, cs_reg_name(handle, op->mem.segment));
-                       if (op->mem.base != X86_REG_INVALID)
-                               pr_dbg2("\t\t\toperands[%u].mem.base: REG = %s\n",
-                                       i, cs_reg_name(handle, op->mem.base));
-                       if (op->mem.index != X86_REG_INVALID)
-                               pr_dbg2("\t\t\toperands[%u].mem.index: REG = %s\n",
-                                       i, cs_reg_name(handle, op->mem.index));
-                       if (op->mem.scale != 1)
-                               pr_dbg2("\t\t\toperands[%u].mem.scale: %u\n",
-                                       i, op->mem.scale);
-                       if (op->mem.disp != 0)
-                               pr_dbg2("\t\t\toperands[%u].mem.disp: 0x%" PRIx64 "\n",
-                                       i, op->mem.disp);
-                       return status;
+                       if (op->mem.base == X86_REG_RIP ||
+                           op->mem.index == X86_REG_RIP)
+                               return CODE_PATCH_NO;
+
+                       status = CODE_PATCH_OK;
+                       break;
                default:
                        break;
                }

@honggyukim
Copy link
Collaborator

Thank you for the work. I just tested it with uftrace itself, but it crashes as follows:

$ uftrace -la -P. ./uftrace --version --no-pager
uftrace: some functions cannot be patched dynamically (518/1241)
child terminated by signal: 11: Segmentation fault
# DURATION     TID     FUNCTION
            [  9146] | _start() {
  37.464 us [  9146] |   __libc_csu_init();
            [  9146] |   main(0xa1aab27f, 0x7ffdf0252148) {
            [  9146] |     argp_parse() {
   1.527 us [  9146] |       parse_option(0x1000003, "NULL", 0x7ffdf0251d00) = 7;
 405.602 us [  9146] |       /* linux:schedule */
 238.133 ms [  9146] |       /* linux:schedule */
            [  9146] |       /* linux:task-exit */

uftrace stopped tracing with remaining functions
================================================
task: 9146
[2] argp_parse
[1] main
[0] _start

@honggyukim
Copy link
Collaborator

honggyukim commented Apr 30, 2019

The gdb example still crashes same as before:

$ uftrace record -P. --no-libcall ./gdb -q -ex="q"
uftrace: some functions cannot be patched dynamically (9536/12554)
child terminated by signal: 11: Segmentation fault

@honggyukim
Copy link
Collaborator

chrome still has the same problem.

$ uftrace record -P. -t 1ms ./chrome --no-sandbox --headless https://www.google.com
uftrace: some functions cannot be patched dynamically (285276/326211)
uftrace: some functions cannot be patched dynamically (285276/326211)
Received signal 4 ILL_ILLOPN 5578bbdaa164
#0 0x7f892392ce27 backtrace
#1 0x5578be09a14f (/home/honggyu/work/chromium/master/src/out/Release/chrome+0x4ca214e)
#2 0x5578be099cb1 (/home/honggyu/work/chromium/master/src/out/Release/chrome+0x4ca1cb0)
#3 0x7f8923512390 <unknown>
...

@honggyukim
Copy link
Collaborator

honggyukim commented Apr 30, 2019

gdb is crashed even only with main is used.

$ uftrace record -P main --no-libcall ./gdb -q -ex="q"
child terminated by signal: 11: Segmentation fault

Here is more verbose message.

$ uftrace record -P main --no-libcall --debug-domain mcount:3 ./gdb -q -ex="q"
mcount: initializing mcount library
mcount: mcount setup done
mcount: new session started: 004f45588847ecaf: gdb.kim
mcount: preparing shmem buffers: tid = 29595
mcount: <0> enter 40b785
mcount:  tr->flags: 0, filter mode, count: [0] 0/0
mcount: task 29595 recorded 16 bytes (record count = 1)
mcount: rstack[0] ENTRY 40b785
Segmentation fault: address not mapped (addr: 0x90)
Backtrace from uftrace:
=====================================
[0] (main[40b785] <= __libc_start_main[7fbcdab24830])
child terminated by signal: 11: Segmentation fault
00000000040b780 <main>:
  40b780:   48 83 ec 28             sub    $0x28,%rsp
  40b784:   48 c7 04 24 00 00 00    movq   $0x0,(%rsp)
  40b78b:   00
  40b78c:   89 3c 24                mov    %edi,(%rsp)
  40b78f:   48 89 e7                mov    %rsp,%rdi
  40b792:   48 89 74 24 08          mov    %rsi,0x8(%rsp)
  40b797:   48 c7 44 24 10 a4 28    movq   $0x7828a4,0x10(%rsp)
  40b79e:   78 00
  40b7a0:   e8 bb 2c 17 00          callq  57e460 <gdb_main(captured_main_args*)>
  40b7a5:   48 83 c4 28             add    $0x28,%rsp
  40b7a9:   c3                      retq
  40b7aa:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

@honggyukim
Copy link
Collaborator

honggyukim commented Apr 30, 2019

It seems that almost every function makes the problem.

$ uftrace record -P gdb_main --no-libcall --debug-domain mcount:3 ./gdb -q -ex="q"
mcount: initializing mcount library
mcount: mcount setup done
mcount: new session started: 73ef104917585de5: gdb.kim
mcount: preparing shmem buffers: tid = 30554
mcount: <0> enter 57e465
mcount:  tr->flags: 0, filter mode, count: [0] 0/0
mcount: task 30554 recorded 16 bytes (record count = 1)
mcount: rstack[0] ENTRY 57e465
Segmentation fault: address not mapped (addr: 0x90)
Backtrace from uftrace:
=====================================
[0] (gdb_main[57e465] <= main[40b7a5])
child terminated by signal: 11: Segmentation fault
$ uftrace record --no-libcall -P make_command_stats_cleanup ./gdb -q -ex="q"
child terminated by signal: 11: Segmentation fault
00000000005c6320 <make_command_stats_cleanup(int)>:
  5c6320:   55                      push   %rbp
  5c6321:   89 fd                   mov    %edi,%ebp
  5c6323:   53                      push   %rbx
  5c6324:   48 83 ec 18             sub    $0x18,%rsp
  5c6328:   85 ff                   test   %edi,%edi
  5c632a:   0f 84 a8 00 00 00       je     5c63d8 <make_command_stats_cleanup(int)+0xb8>
  5c6330:   44 8b 05 69 2d 69 00    mov    0x692d69(%rip),%r8d        # c590a0 <per_command_time>
  5c6337:   45 85 c0                test   %r8d,%r8d
  5c633a:   75 18                   jne    5c6354 <make_command_stats_cleanup(int)+0x34>
  5c633c:   8b 3d 5a 2d 69 00       mov    0x692d5a(%rip),%edi        # c5909c <per_command_space>
  5c6342:   85 ff                   test   %edi,%edi
  5c6344:   75 0e                   jne    5c6354 <make_command_stats_cleanup(int)+0x34>
  5c6346:   8b 35 4c 2d 69 00       mov    0x692d4c(%rip),%esi        # c59098 <per_command_symtab>
  5c634c:   85 f6                   test   %esi,%esi
...

Hmm.. the following case for notice_open_fds doesn't look like a normal function and it doesn't have return instruction inside. Instead, it directly jumps to fdwalk and doesn't come back.

$ uftrace record --no-libcall -P notice_open_fds ./gdb -q -ex="q"
child terminated by signal: 11: Segmentation fault
00000000057c5c0 <notice_open_fds()>:
  57c5c0:   bf 40 c5 57 00          mov    $0x57c540,%edi
  57c5c5:   e9 26 fe ff ff          jmpq   57c3f0 <fdwalk(int (*)(void*, int), void*) [clone .constprop.0]>
  57c5ca:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

...
000000000057c3f0 <fdwalk(int (*)(void*, int), void*) [clone .constprop.0]>:
  57c3f0:   41 56                   push   %r14
  57c3f2:   49 89 fe                mov    %rdi,%r14
  57c3f5:   bf 54 d1 7f 00          mov    $0x7fd154,%edi
  57c3fa:   41 55                   push   %r13
  57c3fc:   41 54                   push   %r12
  57c3fe:   55                      push   %rbp
  57c3ff:   53                      push   %rbx
  57c400:   48 81 ec a0 00 00 00    sub    $0xa0,%rsp
  57c407:   e8 34 cd e8 ff          callq  409140 <opendir@plt>
  57c40c:   48 85 c0                test   %rax,%rax
  57c40f:   49 89 c4                mov    %rax,%r12
...

@honggyukim
Copy link
Collaborator

honggyukim commented May 1, 2019

I don't know why the following change is included in the commit for test.

  • 817cd4d test: add test case for full-dynamic feature.
diff --git a/arch/x86_64/mcount-insn.c b/arch/x86_64/mcount-insn.c
index c81c710..9558e3d 100644
--- a/arch/x86_64/mcount-insn.c
+++ b/arch/x86_64/mcount-insn.c
@@ -155,7 +155,7 @@ int disasm_check_insns(struct mcount_disasm_engine *disasm,
                }
        }

-       while (i < count) {
+       while (++i < count) {
                if (check_unsupported(disasm, &insn[i], addr, code_size) == CODE_PATCH_NO) {
                        ret = INSTRUMENT_FAILED;
                        break;

It's better to be included in the previous commit.

@honggyukim
Copy link
Collaborator

I would like to have the following change for better understanding the details.

diff --git a/arch/x86_64/mcount-dynamic.c b/arch/x86_64/mcount-dynamic.c
index a40f3c2..bb1a462 100644
--- a/arch/x86_64/mcount-dynamic.c
+++ b/arch/x86_64/mcount-dynamic.c
@@ -423,8 +423,8 @@ static int patch_normal_func(struct mcount_dynamic_info *mdi, struct sym *sym,
        if (instr_size < CALL_INSN_SIZE)
                return instr_size;

-       pr_dbg2("patch normal func: %s (patch size: %d)\n",
-               sym->name, instr_size);
+       pr_dbg2("patch normal func \"%s\": %d bytes from %#x to %#x\n",
+               sym->name, instr_size, sym->addr, sym->addr + instr_size - 1);

        /*
         *  stored origin instruction block:
diff --git a/libmcount/dynamic.c b/libmcount/dynamic.c
index 90a6dfb..c33ac45 100644
--- a/libmcount/dynamic.c
+++ b/libmcount/dynamic.c
@@ -288,6 +288,9 @@ static int do_dynamic_update(struct symtabs *symtabs, char *patch_funcs,
                pr_out("uftrace: some functions cannot be patched dynamically (%d/%d)\n",
                       stats.total - (stats.failed + stats.skipped), stats.total);
        }
+       else {
+               pr_out("uftrace: %d function(s) are dynamically patched\n", stats.total);
+       }

        strv_free(&funcs);
        return 0;

@honggyukim
Copy link
Collaborator

Ah.. It's not a problem of dynamic tracing. It seems that gdb internally triggers some SIGABRT and SIGSEGV on purpose, but uftrace registers the handlers for them. I applied the following change to avoid this.

diff --git a/libmcount/mcount.c b/libmcount/mcount.c
index 3d2cd8e..fd47cf9 100644
--- a/libmcount/mcount.c
+++ b/libmcount/mcount.c
@@ -747,8 +747,8 @@ static void mcount_init_file(void)
               SESSION_ID_LEN, mcount_session_name(), basename(mcount_exename));

        sigemptyset(&sa.sa_mask);
-       sigaction(SIGABRT, &sa, &old_sigact[0]);
-       sigaction(SIGSEGV, &sa, &old_sigact[1]);
+//     sigaction(SIGABRT, &sa, &old_sigact[0]);
+//     sigaction(SIGSEGV, &sa, &old_sigact[1]);
 }

 struct mcount_thread_data * mcount_prepare(void)

Then the problem is gone and works fine.

$ uftrace record -P. -la ./gdb -q -ex="q"
uftrace: some functions cannot be patched dynamically (9536/12554)

@honggyukim
Copy link
Collaborator

honggyukim commented May 1, 2019

In the previous crashed backtrace, it shows save_original_signals_state.

uftrace stopped tracing with remaining functions
================================================
task: 46638
[5] internal_vproblem
[4] internal_verror
[3] internal_error
[2] save_original_signals_state
[1] gdb_main
[0] main

The problematic code in gdb is as follows:

#ifdef HAVE_SIGACTION
static struct sigaction original_signal_actions[NSIG];

/* Note that we use sigprocmask without worrying about threads because
   the save/restore functions are called either from main, or after a
   fork.  In both cases, we know the calling process is single
   threaded.  */
static sigset_t original_signal_mask;
#endif

void save_original_signals_state (void)
{
#ifdef HAVE_SIGACTION
  int i;
  int res;

  res = sigprocmask (0,  NULL, &original_signal_mask);
  if (res == -1)
    perror_with_name (("sigprocmask"));

  for (i = 1; i < NSIG; i++)
    {
      struct sigaction *oldact = &original_signal_actions[i];

      res = sigaction (i, NULL, oldact);
      if (res == -1 && errno == EINVAL)
        {
          /* Some signal numbers in the range are invalid.  */
          continue;
        }
      else if (res == -1)
        perror_with_name (("sigaction"));

      /* If we find a custom signal handler already installed, then
         this function was called too late.  */
      if (oldact->sa_handler != SIG_DFL && oldact->sa_handler != SIG_IGN)
        internal_error (__FILE__, __LINE__, _("unexpected signal handler"));
    }
#endif
}

It seems to be a pair with restore_original_signals_state.

void restore_original_signals_state (void)
{
#ifdef HAVE_SIGACTION
  int i;
  int res;

  for (i = 1; i < NSIG; i++)
    {
      res = sigaction (i, &original_signal_actions[i], NULL);
      if (res == -1 && errno == EINVAL)
        {
          /* Some signal numbers in the range are invalid.  */
          continue;
        }
      else if (res == -1)
        perror_with_name (("sigaction"));
    }

  res = sigprocmask (SIG_SETMASK,  &original_signal_mask, NULL);
  if (res == -1)
    perror_with_name (("sigprocmask"));
#endif
}

binutils-gdb/gdb/common/signals-state-save-restore.c

@namhyung
Copy link
Owner

namhyung commented May 1, 2019

It's better to be included in the previous commit.

Yes, that's my fault. Will move.

Ah.. It's not a problem of dynamic tracing. It seems that gdb internally triggers some SIGABRT and SIGSEGV on purpose ...

Thanks for the analysis! That's good to know. We may provide an option to suppress signal handling.

Now it seems good to merge this series...

@namhyung
Copy link
Owner

namhyung commented May 1, 2019

I'm thinking of adding this code instead. Note that it's changed from pr_out to pr_dbg.

diff --git a/libmcount/dynamic.c b/libmcount/dynamic.c
index 5c917a6..1656754 100644
--- a/libmcount/dynamic.c
+++ b/libmcount/dynamic.c
@@ -285,7 +285,8 @@ static int do_dynamic_update(struct symtabs *symtabs, char *patch_funcs,
        }
 
        if (stats.failed || stats.skipped || stats.nomatch) {
-               pr_out("uftrace: some functions cannot be patched dynamically (%d/%d)\n",
+               pr_dbg("uftrace: failed to patch some functions in '%s' (%d/%d)\n",
+                      basename(symtabs->filename),
                       stats.total - (stats.failed + stats.skipped), stats.total);
        }
 

@honggyukim
Copy link
Collaborator

I think we better show the debug message without if condition check. Even if every function is patched, I may be curious how many functions are patched.

@honggyukim
Copy link
Collaborator

Now it seems good to merge this series...

I also agree. :)

@honggyukim
Copy link
Collaborator

honggyukim commented May 1, 2019

Ah.. It's not a problem of dynamic tracing. It seems that gdb internally triggers some SIGABRT and SIGSEGV on purpose ...

It’s not correct. As shown in the below code, gdb doesn’t allow custom signal handlers previously registered.

  for (i = 1; i < NSIG; i++)
    {
      struct sigaction *oldact = &original_signal_actions[i];

      res = sigaction (i, NULL, oldact);
...
      /* If we find a custom signal handler already installed, then
         this function was called too late.  */
      if (oldact->sa_handler != SIG_DFL && oldact->sa_handler != SIG_IGN)
        internal_error (__FILE__, __LINE__, _("unexpected signal handler"));
    }

I’m not sure if we can wrap the signal and sigaction functions.

@honggyukim
Copy link
Collaborator

It may not be really needed but uftrace record command itself makes infinite loop :)

$ uftrace -P. -a -t 1ms ./uftrace record t-abc
uftrace: some functions cannot be patched dynamically (625/756)
uftrace: some functions cannot be patched dynamically (1/6)
(... running forever ...)

We must not dynamically patch some functions in libmcount.so itself.

@ParkHanbum
Copy link
Contributor Author

ParkHanbum commented May 1, 2019

@honggyukim

It may not be really needed but uftrace record command itself makes infinite loop :)

$ uftrace -P. -a -t 1ms ./uftrace record t-abc
uftrace: some functions cannot be patched dynamically (625/756)
uftrace: some functions cannot be patched dynamically (1/6)
(... running forever ...)

We must not dynamically patch some functions in libmcount.so itself.

it was finished when I use option to specific uftrace data path.

$ uftrace -d outer -P. ./uftrace record -d inner t-abc

maybe it occurred because it associated with stream channel

@honggyukim
Copy link
Collaborator

@ParkHanbum Thanks. It works in my environment as well when -d option is given. But it's strange that I expected t-abc recording uftrace will use uftrace.data in the current directory and the one recording uftrace itself will create a directory in /tmp. It looks great anyway.

@namhyung
Copy link
Owner

namhyung commented May 1, 2019

Pushed review/dynamic-trace-v9

Changelog:

  • fix a bug in checking target address of jump (thanks @honggyukim)
  • move increment of insn index to the proper commit
  • do not use floating-point math in libmcount
  • fix/add some debug messages

@honggyukim
Copy link
Collaborator

honggyukim commented May 1, 2019

Thanks for the update! I found a very weird use case as follows:

$ uftrace record -P. -a ./uftrace record -d abc t-abc

$ uftrace replay -d abc
fstack: /home/honggyu/work/uftrace/utils/fstack.c:1413:read_task_ustack
 ERROR: record missing argument info
# DURATION     TID     FUNCTION
   1.276 us [ 72482] | __monstartup();
   0.843 us [ 72482] | __cxa_atexit();

It seems that the tracing target uftrace received environmental variable passed by -a although it doesn't use such argument option. It can be seen as follows:

$ uftrace record -P. -a ./uftrace replay -d abc
# DURATION     TID     FUNCTION
   1.237 us [ 72555] | __monstartup();
   0.880 us [ 72555] | __cxa_atexit();
            [ 72555] | main(1, 0x7ffdcd4b2a58) {
            [ 72555] |   main(1, 0x7ffdcd4b2a58) {
            [ 72555] |     a() {
            [ 72555] |       b() {
            [ 72555] |         c() {
   0.764 us [ 72555] |           getpid() = 72555;
   6.006 us [ 72555] |         } = 72555; /* c */
   7.073 us [ 72555] |       } = 72556; /* b */
   7.857 us [ 72555] |     } = 72555; /* a */
   8.774 us [ 72555] |   } = 0; /* main */
  10.667 us [ 72555] | } = 0; /* main */

The uftrace record for uftrace record is fine anyway.

@honggyukim
Copy link
Collaborator

honggyukim commented May 1, 2019

The above is a different issue. I think it almost looks good and I agree to merge this into master. We may be able to enhance it in the next patches.

I think we could somehow handle gdb signal handling problem, but it's not a dynamic tracing issue. So let's also fix this later on.

@honggyukim
Copy link
Collaborator

Here is the trace dump of uftrace live command that both covers record and replay. You can see the running details of uftrace visually.
https://uftrace.github.io/dump/uftrace-live.html

Thanks for doing this great work @ParkHanbum and @namhyung!

@honggyukim
Copy link
Collaborator

Please add the following change as well for easier installation.

diff --git a/misc/install-deps.sh b/misc/install-deps.sh
index a1b01d96..62e084ac 100755
--- a/misc/install-deps.sh
+++ b/misc/install-deps.sh
@@ -13,12 +13,12 @@ distro=$(grep "^ID=" /etc/os-release | cut -d\= -f2 | sed -e 's/"//g')

 case $distro in
     "ubuntu" | "debian")
-        apt-get $OPT install pandoc libdw-dev libpython2.7-dev libncursesw5-dev pkg-config ;;
+        apt-get $OPT install pandoc libdw-dev libpython2.7-dev libncursesw5-dev libcapstone-dev pkg-config ;;
     "fedora")
-        dnf install $OPT pandoc elfutils-devel python2-devel ncurses-devel pkgconf-pkg-config ;;
+        dnf install $OPT pandoc elfutils-devel python2-devel ncurses-devel capstone-devel pkgconf-pkg-config ;;
     "rhel" | "centos")
         rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
-        yum install $OPT pandoc elfutils-devel python2-devel ncurses-devel pkgconfig ;;
+        yum install $OPT pandoc elfutils-devel python2-devel ncurses-devel capstone-devel pkgconfig ;;
     *) # we can add more install command for each distros.
         echo "\"$distro\" is not supported distro, so please install packages manually." ;;
 esac

Confirmed in Ubuntu and Fedora, but hope it to be correct in RHEL/CentOS.

@namhyung
Copy link
Owner

namhyung commented May 2, 2019

Good, can I add your signed-off?

@honggyukim
Copy link
Collaborator

Sure. Please do that. Thanks.

@namhyung namhyung unpinned this issue May 2, 2019
@honggyukim
Copy link
Collaborator

honggyukim commented May 2, 2019

Thanks very much! We better update dynamic tracing section in the man page before making a release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants