process_tail 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b004b93e9fbf8b7ac0ba96cf4f575fd9e3dc859b
4
- data.tar.gz: 63c084db42e4d379f29bb2e246a9965b4b07c9dc
3
+ metadata.gz: 162ae595f4fb2f2ae422ebfe8a302eac30ae9646
4
+ data.tar.gz: 66d884803130a2e7a1f0852be873b308f0d914a3
5
5
  SHA512:
6
- metadata.gz: fc6aeab101d948a5cf95867f8d7dea3b36d6a04a9c072970e0fd76286b556b31c590841a7e4794edec34cbc29f434368b2b2e8824cc8c24595b28fecc3743ae3
7
- data.tar.gz: 22a7ae2f2c4e6b6c7b5d052be7c9655afa01398906a5fc41471d099630976acdf829e8987d38fd25995ced555a85299d19749034de1560c34650749e417e5b96
6
+ metadata.gz: 39872b3eb28cea4a41f3299633e7c497e5cd01526f7f4670679efeb54a13cf362f0b6597fc26b6ff7f449f7fafbe443822768b54b3a502f8724f13abd0f88ba2
7
+ data.tar.gz: ba405708d7b4627c94a3ce9265947db64c7293f78f02d2b2674a1a25cb700234a692d2a79689f68e3ed47a07fd12511a4568dac6ef667d2a09e7053d6dd8b79a
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  Vagrantfile
16
+ Makefile
17
+ extconf.h
data/README.md CHANGED
@@ -5,12 +5,12 @@ https://github.com/hibariya/process_tail
5
5
  ## Description
6
6
 
7
7
  ProcessTail traces other process' write(2) system call and copy its buffer.
8
- So you can get other process outputs.
8
+ So we can get other process outputs.
9
9
 
10
10
  ## Problems
11
11
 
12
- * Mac OSX and Windows are not supported at the moment
13
- * SEGV occures occasionally
12
+ * SEGV occurs infrequently
13
+ * Windows is not supported
14
14
 
15
15
  ## Installation
16
16
 
@@ -30,11 +30,15 @@ Or install it yourself as:
30
30
 
31
31
  ## Usage
32
32
 
33
+ On Mac OSX, you'll have to use `sudo`.
34
+
33
35
  ### Get outputs as an IO object
34
36
 
35
37
  ```ruby
36
- ProcessTail.open pid, :stdout do |io|
37
- puts "Recent stdout of #{pid}: #{io.gets}"
38
+ ProcessTail.each pid do |tid, fd, str|
39
+ puts tid # pid or tid of this process (thread)
40
+ puts fd # File descripter number
41
+ puts str # Output string
38
42
  end
39
43
  ```
40
44
 
@@ -4,12 +4,6 @@ require 'process_tail'
4
4
 
5
5
  $stdout.sync = true
6
6
 
7
- ProcessTail.open Integer(ARGV[0]), :all do |io|
8
- begin
9
- while c = io.getc
10
- print c
11
- end
12
- rescue IOError
13
- # NOP
14
- end
7
+ ProcessTail.each Integer(ARGV[0]) do |tid, fd, str|
8
+ print str if fd == 1 || fd == 2
15
9
  end
@@ -1,17 +1,38 @@
1
1
  require 'mkmf'
2
2
 
3
- %w(string.h sys/ptrace.h sys/syscall.h sys/types.h sys/user.h sys/wait.h ruby.h ruby/thread.h).each do |h|
4
- abort "missing #{h}" unless have_header(h)
5
- end
3
+ objs = []
6
4
 
7
- %w(orig_rax rdi rdx rsi rdx).each do |m|
8
- abort "missing #{m}" unless have_struct_member('struct user_regs_struct', m, 'sys/user.h')
9
- end
5
+ -> {
6
+ return unless have_library('dtrace', 'dtrace_open', 'dtrace.h')
10
7
 
11
- %w(PTRACE_O_TRACESYSGOOD PTRACE_O_TRACEFORK PTRACE_O_TRACEVFORK PTRACE_O_TRACECLONE).each do |c|
12
- abort "missing #{c}" unless have_const(c, 'sys/ptrace.h')
13
- end
8
+ have_header 'dtrace.h'
14
9
 
15
- abort 'missing __WALL' unless have_const('__WALL', 'sys/wait.h')
10
+ objs << 'pt_dtrace.o'
11
+ }.()
16
12
 
13
+ -> {
14
+ return unless %w(orig_rax rdi rdx rsi rdx).all? {|m|
15
+ have_struct_member('struct user_regs_struct', m, 'sys/user.h')
16
+ }
17
+
18
+ return unless %w(sys/syscall.h sys/types.h sys/user.h sys/wait.h).all? {|h|
19
+ have_header(h)
20
+ }
21
+
22
+ return unless have_const('__WALL', 'sys/wait.h')
23
+
24
+ return unless %w(PTRACE_O_TRACESYSGOOD PTRACE_O_TRACEFORK PTRACE_O_TRACEVFORK PTRACE_O_TRACECLONE).all? {|c|
25
+ have_const(c, 'sys/ptrace.h')
26
+ }
27
+
28
+ have_header 'sys/ptrace.h'
29
+
30
+ objs << 'pt_ptrace.o'
31
+ }.()
32
+
33
+ abort 'No available libraries' if objs.empty?
34
+
35
+ $objs = %w(process_tail.o pt_tracee.o) + objs
36
+
37
+ create_header
17
38
  create_makefile 'process_tail/process_tail'
@@ -1,268 +1,126 @@
1
- #include <stdlib.h>
2
- #include <string.h>
3
- #include <sys/ptrace.h>
4
- #include <sys/syscall.h>
5
1
  #include <sys/types.h>
6
- #include <sys/user.h>
7
- #include <sys/wait.h>
8
2
  #include <ruby.h>
9
- #include <ruby/thread.h>
10
-
11
- typedef struct pt_tracee {
12
- pid_t pid;
13
- int activated;
14
- int dead;
15
- struct pt_tracee *next;
16
- } pt_tracee_t;
17
-
18
- typedef struct {
19
- int *status;
20
- pid_t pid;
21
- } pt_wait_args_t;
22
-
23
- typedef struct {
24
- unsigned int fd;
25
- pt_tracee_t *tracee;
26
- VALUE *wait_queue;
27
- } pt_loop_args_t;
3
+ #include "extconf.h"
4
+ #include "process_tail.h"
5
+ #include "pt_tracee.h"
6
+ #ifdef HAVE_SYS_PTRACE_H
7
+ #include "pt_ptrace.h"
8
+ #endif
9
+ #ifdef HAVE_DTRACE_H
10
+ #include "pt_dtrace.h"
11
+ #endif
28
12
 
29
13
  static void
30
- pt_tracee_initialize(pt_tracee_t *tracee)
31
- {
32
- tracee->pid = 0;
33
- tracee->activated = 0;
34
- tracee->dead = 0;
35
- tracee->next = NULL;
36
- }
37
-
38
- static pt_tracee_t *
39
- pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
14
+ pt_process_tail_mark(pt_process_tail_t *ptp)
40
15
  {
41
- pt_tracee_t *tracee = malloc(sizeof(pt_tracee_t));
42
-
43
- if (tracee == NULL) {
44
- exit(EXIT_FAILURE);
45
- }
46
-
47
- pt_tracee_initialize(tracee);
48
- tracee->pid = pid;
49
- tracee->next = (struct pt_tracee *)*headpp;
50
-
51
- (*headpp) = tracee;
52
-
53
- return tracee;
16
+ rb_gc_mark(ptp->proc);
17
+ rb_gc_mark(ptp->trace_thread);
18
+ rb_gc_mark(ptp->wait_queue);
54
19
  }
55
20
 
56
- static pt_tracee_t *
57
- pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid)
21
+ static VALUE
22
+ pt_process_tail_alloc(VALUE klass)
58
23
  {
59
- pt_tracee_t *headp = *headpp;
60
- pt_tracee_t *current = headp;
61
-
62
- while (current) {
63
- if (current->pid == pid) {
64
- return current;
65
- }
24
+ pt_process_tail_t *ptp = ALLOC(pt_process_tail_t);
66
25
 
67
- current = current->next;
68
- }
69
-
70
- return pt_tracee_add(headpp, pid);
26
+ return Data_Wrap_Struct(klass, pt_process_tail_mark, RUBY_DEFAULT_FREE, ptp);
71
27
  }
72
28
 
73
- static int
74
- pt_tracee_wipedoutq(pt_tracee_t *headp)
29
+ static VALUE
30
+ pt_process_tail_initialize(VALUE self, VALUE pidv, VALUE fdv)
75
31
  {
76
- pt_tracee_t *current = headp;
32
+ pt_process_tail_t *ptp;
77
33
 
78
- while (current) {
79
- if (current->dead == 0) {
80
- return 0;
81
- }
82
-
83
- current = current->next;
34
+ if (!rb_block_given_p()) {
35
+ rb_raise(rb_eArgError, "no block given");
84
36
  }
85
37
 
86
- return 1;
87
- }
88
-
89
- static void
90
- pt_tracee_free(pt_tracee_t **headpp)
91
- {
92
- pt_tracee_t *tracee = *headpp;
93
-
94
- if (tracee) {
95
- pt_tracee_free(&tracee->next);
38
+ Check_Type(pidv, T_FIXNUM);
39
+ Check_Type(fdv, T_FIXNUM);
96
40
 
97
- free(tracee);
98
- tracee = NULL;
99
- }
100
- }
41
+ Data_Get_Struct(self, pt_process_tail_t, ptp);
101
42
 
102
- static void *
103
- pt_wait(void *argp)
104
- {
105
- pt_wait_args_t *args = (pt_wait_args_t *)argp;
43
+ ptp->pid = (pid_t)NUM2INT(pidv);
44
+ ptp->fd = (unsigned int)NUM2INT(fdv);
45
+ ptp->proc = rb_block_proc();
46
+ ptp->trace_thread = (VALUE)NULL;
47
+ ptp->parent_thread = (VALUE)NULL;
48
+ ptp->wait_queue = (VALUE)NULL;
49
+ ptp->tracee = NULL;
106
50
 
107
- args->pid = waitpid(-1, args->status, __WALL);
108
-
109
- return NULL;
51
+ return Qnil;
110
52
  }
111
53
 
112
- static void
113
- pt_read_data(pid_t pid, long addr, char *string, int length)
54
+ static VALUE
55
+ pt_process_tail_pid_reader(VALUE self)
114
56
  {
115
- int i;
116
- long data;
57
+ pt_process_tail_t *ptp;
117
58
 
118
- for (i = 0; i < length; i += sizeof(long)) {
119
- data = ptrace(PTRACE_PEEKDATA, pid, addr + i);
59
+ Data_Get_Struct(self, pt_process_tail_t, ptp);
120
60
 
121
- memcpy(string + i, &data, sizeof(long));
122
- }
123
-
124
- string[length] = '\0';
125
- }
126
-
127
- static void
128
- pt_ptrace_syscall(pid_t pid, long signal)
129
- {
130
- ptrace(PTRACE_SYSCALL, pid, 0, signal == SIGTRAP ? 0 : signal);
131
-
132
- rb_thread_schedule();
61
+ return INT2NUM((int)ptp->pid);
133
62
  }
134
63
 
135
64
  static VALUE
136
- pt_loop(VALUE loop_args)
65
+ pt_process_tail_fd_reader(VALUE self)
137
66
  {
138
- pt_loop_args_t *args = (void *)loop_args;
139
-
140
- char *string = NULL;
141
- int syscall = 0;
142
- int status;
143
- pid_t pid;
144
- pt_tracee_t *tracee;
145
- pt_wait_args_t pt_wait_args = {&status};
146
-
147
- // NOTE: system call: orig_rax, arguments: rdi, rsi, rdx, r10, r8, r9
148
- struct user_regs_struct regs;
149
-
150
- while (1) {
151
- rb_thread_call_without_gvl(pt_wait, &pt_wait_args, RUBY_UBF_PROCESS, NULL);
152
-
153
- pid = pt_wait_args.pid;
154
- tracee = pt_tracee_find_or_add(&args->tracee, pid);
67
+ pt_process_tail_t *ptp;
155
68
 
156
- if (tracee->activated == 0) {
157
- tracee->activated = 1;
158
- rb_funcall(*args->wait_queue, rb_intern("enq"), 1, INT2FIX((int)pid));
69
+ Data_Get_Struct(self, pt_process_tail_t, ptp);
159
70
 
160
- ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE);
161
- pt_ptrace_syscall(pid, 0);
162
-
163
- continue;
164
- }
165
-
166
- if (WIFEXITED(status) || WIFSIGNALED(status)) {
167
- tracee->dead = 1;
168
-
169
- if (pt_tracee_wipedoutq(args->tracee)) {
170
- return Qnil;
171
- }
172
-
173
- continue;
174
- }
175
-
176
- if (!(WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)) {
177
- pt_ptrace_syscall(pid, WSTOPSIG(status));
178
-
179
- continue;
180
- }
181
-
182
- ptrace(PTRACE_GETREGS, pid, 0, &regs);
183
-
184
- if (regs.orig_rax != SYS_write) {
185
- pt_ptrace_syscall(pid, 0);
186
-
187
- continue;
188
- }
189
-
190
- syscall = 1 - syscall;
191
- if (syscall && (args->fd == 0 || regs.rdi == args->fd)) {
192
- string = malloc(regs.rdx + sizeof(long));
193
-
194
- if (string == NULL) {
195
- exit(EXIT_FAILURE);
196
- }
197
-
198
- pt_read_data(pid, regs.rsi, string, regs.rdx);
199
-
200
- rb_yield(rb_ary_new_from_args(3, rb_str_new_cstr(string), INT2FIX((int)pid), INT2FIX(args->fd)));
201
-
202
- free(string);
203
- }
204
-
205
- pt_ptrace_syscall(pid, 0);
206
- }
207
-
208
- return Qnil;
71
+ return INT2NUM((int)ptp->fd);
209
72
  }
210
73
 
211
74
  static VALUE
212
- pt_attach(VALUE klass, VALUE pidv)
75
+ pt_process_tail_trace_thread_reader(VALUE self)
213
76
  {
214
- int pid = (pid_t)FIX2INT(pidv);
215
-
216
- Check_Type(pidv, T_FIXNUM);
217
-
218
- if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
219
- rb_sys_fail("PTRACE_ATTACH");
77
+ pt_process_tail_t *ptp;
220
78
 
221
- return Qnil;
222
- }
79
+ Data_Get_Struct(self, pt_process_tail_t, ptp);
223
80
 
224
- return Qtrue;
81
+ return ptp->trace_thread;
225
82
  }
226
83
 
227
- static VALUE
228
- pt_detach(VALUE klass, VALUE pidv)
229
- {
230
- Check_Type(pidv, T_FIXNUM);
231
-
232
- ptrace(PTRACE_DETACH, (pid_t)FIX2INT(pidv), NULL, NULL);
84
+ VALUE ProcessTail;
85
+ VALUE ProcessTail_Tracer;
86
+ VALUE ProcessTail_StopTracing;
87
+ VALUE ProcessTail_TraceError;
233
88
 
234
- return Qnil;
89
+ void
90
+ pt_lock_trace(void)
91
+ {
92
+ rb_funcall(PT_TRACE_LOCK, rb_intern("lock"), 0);
235
93
  }
236
94
 
237
- static VALUE
238
- pt_finalize(VALUE traceev)
95
+ void
96
+ pt_unlock_trace(void)
239
97
  {
240
- pt_tracee_t *tracee = (void *)traceev;
241
-
242
- pt_tracee_free(&tracee);
243
-
244
- return Qnil;
98
+ rb_funcall(PT_TRACE_LOCK, rb_intern("unlock"), 0);
245
99
  }
246
100
 
247
- static VALUE
248
- pt_trace(VALUE klass, VALUE fdv, VALUE wait_queue)
101
+ void
102
+ Init_process_tail(void)
249
103
  {
250
- pt_tracee_t *tracee = NULL;
251
- pt_loop_args_t loop_args = {FIX2INT(fdv), tracee, &wait_queue};
104
+ ProcessTail = rb_define_module("ProcessTail");
252
105
 
253
- Check_Type(fdv, T_FIXNUM);
106
+ ProcessTail_StopTracing = rb_define_class_under(ProcessTail, "StopTracing", rb_eStandardError);
107
+ ProcessTail_TraceError = rb_define_class_under(ProcessTail, "TraceError", rb_eStandardError);
254
108
 
255
- rb_ensure(pt_loop, (VALUE)&loop_args, pt_finalize, (VALUE)tracee);
109
+ ProcessTail_Tracer = rb_define_class_under(ProcessTail, "Tracer", rb_cObject);
110
+ rb_define_const(ProcessTail_Tracer, "TRACE_LOCK", rb_class_new_instance(0, NULL, rb_path2class("Mutex")));
256
111
 
257
- return Qnil;
258
- }
112
+ rb_define_alloc_func(ProcessTail_Tracer, pt_process_tail_alloc);
113
+ rb_define_private_method(ProcessTail_Tracer, "initialize", pt_process_tail_initialize, 2);
259
114
 
260
- void
261
- Init_process_tail()
262
- {
263
- VALUE ProcessTail = rb_define_module("ProcessTail");
115
+ rb_define_method(ProcessTail_Tracer, "pid", pt_process_tail_pid_reader, 0);
116
+ rb_define_method(ProcessTail_Tracer, "fd", pt_process_tail_fd_reader, 0);
117
+ rb_define_method(ProcessTail_Tracer, "trace_thread", pt_process_tail_trace_thread_reader, 0);
118
+
119
+ #ifdef HAVE_SYS_PTRACE_H
120
+ rb_define_private_method(ProcessTail_Tracer, "ptrace_attach", pt_ptrace_attach, 0);
121
+ #endif
264
122
 
265
- rb_define_private_method(rb_singleton_class(ProcessTail), "attach", pt_attach, 1);
266
- rb_define_private_method(rb_singleton_class(ProcessTail), "do_trace", pt_trace, 2);
267
- rb_define_private_method(rb_singleton_class(ProcessTail), "detach", pt_detach, 1);
123
+ #ifdef HAVE_DTRACE_H
124
+ rb_define_private_method(ProcessTail_Tracer, "dtrace_attach", pt_dtrace_attach, 0);
125
+ #endif
268
126
  }
@@ -0,0 +1,31 @@
1
+ #ifndef PROCESS_TAIL_H
2
+ #define PROCESS_TAIL_H
3
+ #include <sys/types.h>
4
+ #include <ruby.h>
5
+ #include "extconf.h"
6
+ #include "pt_tracee.h"
7
+
8
+ #define PT_TRACE_LOCK (rb_const_get(ProcessTail_Tracer, rb_intern("TRACE_LOCK")))
9
+
10
+ extern VALUE ProcessTail;
11
+ extern VALUE ProcessTail_Tracer;
12
+ extern VALUE ProcessTail_StopTracing;
13
+ extern VALUE ProcessTail_TraceError;
14
+
15
+ typedef struct {
16
+ pid_t pid;
17
+ unsigned int fd;
18
+ VALUE proc;
19
+ VALUE trace_thread;
20
+ VALUE parent_thread;
21
+ VALUE wait_queue;
22
+ pt_tracee_t *tracee;
23
+ void *data;
24
+ } pt_process_tail_t;
25
+
26
+ void pt_lock_trace(void);
27
+ void pt_unlock_trace(void);
28
+
29
+ void Init_process_tail(void);
30
+
31
+ #endif
@@ -0,0 +1,194 @@
1
+ #include <stdio.h>
2
+ #include <dtrace.h>
3
+ #include <ruby.h>
4
+ #include <ruby/thread.h>
5
+ #include "process_tail.h"
6
+ #include "pt_dtrace.h"
7
+
8
+ static int
9
+ probe_consumer(const dtrace_probedata_t *data, void *argp)
10
+ {
11
+ pt_process_tail_t *pt;
12
+
13
+ dtrace_eprobedesc_t *edesc = data->dtpda_edesc;
14
+ dtrace_recdesc_t *rec;
15
+ caddr_t addr;
16
+
17
+ int i;
18
+ VALUE arg;
19
+ VALUE args = rb_ary_new_capa(3);
20
+
21
+ Data_Get_Struct((VALUE)argp, pt_process_tail_t, pt);
22
+
23
+ for (i = 0; i < edesc->dtepd_nrecs; i++) {
24
+ arg = 0;
25
+ rec = &edesc->dtepd_rec[i];
26
+
27
+ if (rec->dtrd_size > 0) {
28
+ addr = data->dtpda_data + rec->dtrd_offset;
29
+
30
+ switch(rec->dtrd_action) {
31
+ case DTRACEACT_DIFEXPR:
32
+ switch(rec->dtrd_size) {
33
+ case 1:
34
+ arg = INT2NUM((int)(*((uint8_t *)addr)));
35
+ break;
36
+ case 2:
37
+ arg = INT2NUM((int)(*((uint16_t *)addr)));
38
+ break;
39
+ case 4:
40
+ arg = INT2NUM(*((int32_t *)addr));
41
+ break;
42
+ case 8:
43
+ arg = LL2NUM(*((int64_t *)addr));
44
+ break;
45
+ default:
46
+ arg = rb_str_new_cstr((char *)addr);
47
+ }
48
+
49
+ rb_ary_push(args, arg);
50
+ break;
51
+ default:
52
+ break;
53
+ }
54
+ }
55
+ }
56
+
57
+ rb_proc_call(pt->proc, args);
58
+
59
+ return (DTRACE_CONSUME_THIS);
60
+ }
61
+
62
+ static void *
63
+ pt_dtrace_sleep(void *argp)
64
+ {
65
+ dtrace_hdl_t *hdl = (dtrace_hdl_t *)argp;
66
+
67
+ dtrace_sleep(hdl);
68
+
69
+ return NULL;
70
+ }
71
+
72
+ static VALUE
73
+ pt_loop(VALUE self)
74
+ {
75
+ pt_process_tail_t *pt;
76
+ dtrace_hdl_t *hdl;
77
+
78
+ VALUE dscriptv = rb_funcall(self, rb_intern("generate_dscript"), 0);
79
+ dtrace_prog_t *program;
80
+ dtrace_proginfo_t *proginfo;
81
+
82
+ FILE *devnull = fopen("/dev/null", "w");
83
+ int activated = 0;
84
+ int done = 0;
85
+
86
+ Data_Get_Struct(self, pt_process_tail_t, pt);
87
+
88
+ hdl = PT_DTRACE_HDL(pt);
89
+
90
+ dtrace_setopt(hdl, "stacksymbols", "enabled");
91
+ dtrace_setopt(hdl, "bufsize", "4m");
92
+ dtrace_setopt(hdl, "quiet", 0);
93
+
94
+ program = dtrace_program_strcompile(hdl, StringValueCStr(dscriptv), DTRACE_PROBESPEC_NAME, DTRACE_C_PSPEC, 0, NULL);
95
+
96
+ if (!program) {
97
+ rb_raise(ProcessTail_TraceError, "dtrace_program_strcompile: %s", dtrace_errmsg(hdl, dtrace_errno(hdl)));
98
+ return Qnil;
99
+ }
100
+
101
+ if (dtrace_program_exec(hdl, program, proginfo) < 0) {
102
+ rb_raise(ProcessTail_TraceError, "dtrace_program_exec: %s", dtrace_errmsg(hdl, dtrace_errno(hdl)));
103
+ return Qnil;
104
+ }
105
+
106
+ if (dtrace_go(hdl) < 0) {
107
+ rb_raise(ProcessTail_TraceError, "dtrace_go: %s", dtrace_errmsg(hdl, dtrace_errno(hdl)));
108
+ return Qnil;
109
+ }
110
+
111
+ do {
112
+ if (!done) {
113
+ if (!activated) {
114
+ rb_funcall(pt->wait_queue, rb_intern("enq"), 1, INT2NUM((int)pt->pid));
115
+
116
+ activated = 1;
117
+ }
118
+
119
+ rb_thread_call_without_gvl(pt_dtrace_sleep, hdl, RUBY_UBF_PROCESS, NULL);
120
+ }
121
+
122
+ if (done) {
123
+ if(dtrace_stop(hdl) == -1) {
124
+ rb_raise(ProcessTail_TraceError, "dtrace_stop: %s", dtrace_errmsg(hdl, dtrace_errno(hdl)));
125
+ return Qnil;
126
+ }
127
+ }
128
+
129
+ switch (dtrace_work(hdl, devnull, probe_consumer, NULL, (void *)self)) {
130
+ case DTRACE_WORKSTATUS_DONE:
131
+ done = 1;
132
+ break;
133
+ case DTRACE_WORKSTATUS_OKAY:
134
+ break;
135
+ default:
136
+ break;
137
+ }
138
+
139
+ rb_thread_schedule();
140
+ } while(!done);
141
+
142
+ return Qnil;
143
+ }
144
+
145
+ static VALUE
146
+ pt_finalize(VALUE self)
147
+ {
148
+ pt_process_tail_t *pt;
149
+
150
+ Data_Get_Struct(self, pt_process_tail_t, pt);
151
+
152
+ dtrace_close(PT_DTRACE_HDL(pt));
153
+
154
+ pt_unlock_trace();
155
+ rb_funcall(pt->parent_thread, rb_intern("raise"), 1, ProcessTail_StopTracing);
156
+
157
+ return Qnil;
158
+ }
159
+
160
+ static VALUE
161
+ pt_loop_thread(void *self)
162
+ {
163
+ pt_lock_trace();
164
+
165
+ rb_ensure(pt_loop, (VALUE)self, pt_finalize, (VALUE)self);
166
+
167
+ return Qnil;
168
+ }
169
+
170
+ VALUE
171
+ pt_dtrace_attach(VALUE self)
172
+ {
173
+ pt_process_tail_t *pt;
174
+
175
+ int error;
176
+ dtrace_hdl_t *hdl = dtrace_open(DTRACE_VERSION, 0, &error);
177
+
178
+ Data_Get_Struct(self, pt_process_tail_t, pt);
179
+
180
+ if (!hdl) {
181
+ rb_raise(ProcessTail_TraceError, "dtrace_open: %s", strerror(error));
182
+ return Qnil;
183
+ }
184
+
185
+ pt->tracee = NULL; // NOT USED
186
+ pt->data = (void *)hdl;
187
+ pt->wait_queue = rb_class_new_instance(0, NULL, rb_path2class("Queue"));
188
+ pt->parent_thread = rb_thread_current();
189
+ pt->trace_thread = rb_thread_create(pt_loop_thread, (void *)self);
190
+
191
+ rb_funcall(pt->wait_queue, rb_intern("deq"), 0);
192
+
193
+ return pt->trace_thread;
194
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef PT_DTRACE_H
2
+ #define PT_DTRACE_H
3
+
4
+ #include <ruby.h>
5
+
6
+ #define PT_DTRACE_HDL(pt) ((dtrace_hdl_t *)pt->data)
7
+
8
+ VALUE pt_dtrace_attach(VALUE self);
9
+
10
+ #endif
@@ -0,0 +1,234 @@
1
+ #include <stdlib.h>
2
+ #include <string.h>
3
+ #include <dirent.h>
4
+ #include <sys/ptrace.h>
5
+ #include <sys/syscall.h>
6
+ #include <sys/types.h>
7
+ #include <sys/user.h>
8
+ #include <sys/wait.h>
9
+ #include <ruby.h>
10
+ #include <ruby/thread.h>
11
+ #include "process_tail.h"
12
+ #include "pt_tracee.h"
13
+ #include "pt_ptrace.h"
14
+
15
+ typedef struct {
16
+ int *status;
17
+ pid_t pid;
18
+ } pt_wait_args_t;
19
+
20
+ static pt_tracee_t *
21
+ pt_extract_tracee_list(pid_t pid)
22
+ {
23
+ pt_tracee_t *tracee = NULL;
24
+ char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
25
+ DIR *dir;
26
+ struct dirent *entry;
27
+ pid_t tid;
28
+
29
+ sprintf(procdir, "/proc/%d/task", pid);
30
+ dir = opendir(procdir);
31
+
32
+ while ((entry = readdir(dir)) != NULL) {
33
+ if (entry->d_fileno == 0) {
34
+ continue;
35
+ }
36
+
37
+ tid = (pid_t)atoi(entry->d_name);
38
+
39
+ if (tid <= 0) {
40
+ continue;
41
+ }
42
+
43
+ tracee = pt_tracee_find_or_add(&tracee, tid);
44
+ }
45
+
46
+ closedir(dir);
47
+
48
+ return tracee;
49
+ }
50
+
51
+ static void *
52
+ pt_wait(void *argv)
53
+ {
54
+ pt_wait_args_t *args = (pt_wait_args_t *)argv;
55
+
56
+ args->pid = waitpid(-1, args->status, __WALL);
57
+
58
+ return NULL;
59
+ }
60
+
61
+ static void
62
+ pt_read_data(pid_t pid, long addr, char *string, int length)
63
+ {
64
+ int i;
65
+ long data;
66
+
67
+ for (i = 0; i < length; i += sizeof(long)) {
68
+ data = ptrace(PTRACE_PEEKDATA, pid, addr + i);
69
+
70
+ memcpy(string + i, &data, sizeof(long));
71
+ }
72
+
73
+ string[length] = '\0';
74
+ }
75
+
76
+ static void
77
+ pt_ptrace_syscall(pid_t pid, long signal)
78
+ {
79
+ ptrace(PTRACE_SYSCALL, pid, 0, signal == SIGTRAP || signal == SIGSTOP ? 0 : signal);
80
+
81
+ rb_thread_schedule();
82
+ }
83
+
84
+ static int
85
+ pt_ptrace_attach_multi(pt_tracee_t *tracee)
86
+ {
87
+ do {
88
+ if (ptrace(PTRACE_ATTACH, tracee->pid, NULL, NULL) < 0) {
89
+ return 0;
90
+ }
91
+ } while ((tracee = tracee->next) != NULL);
92
+
93
+ return 1;
94
+ }
95
+
96
+ static VALUE
97
+ pt_loop(VALUE self)
98
+ {
99
+ pt_process_tail_t *pt;
100
+
101
+ pt_tracee_t *tracee;
102
+
103
+ pid_t pid;
104
+ int status;
105
+ pt_wait_args_t wait_args = {&status};
106
+
107
+ int syscall = 0;
108
+ char *string = NULL;
109
+
110
+ struct user_regs_struct regs; // NOTE: system call: orig_rax, arguments: rdi, rsi, rdx, r10, r8, r9
111
+
112
+ Data_Get_Struct(self, pt_process_tail_t, pt);
113
+
114
+ if (!pt_ptrace_attach_multi(pt->tracee)) {
115
+ rb_raise(ProcessTail_TraceError, "PTRACE_ATTACH");
116
+
117
+ return Qnil;
118
+ }
119
+
120
+ while (1) {
121
+ rb_thread_call_without_gvl(pt_wait, &wait_args, RUBY_UBF_PROCESS, NULL);
122
+
123
+ pid = wait_args.pid;
124
+ tracee = pt_tracee_find_or_add(&pt->tracee, pid);
125
+
126
+ if (tracee->activated == 0) {
127
+ tracee->activated = 1;
128
+ rb_funcall(pt->wait_queue, rb_intern("enq"), 1, INT2NUM((int)pid));
129
+
130
+ ptrace(PTRACE_SETOPTIONS, pid, 0,
131
+ PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE
132
+ );
133
+
134
+ pt_ptrace_syscall(pid, 0);
135
+
136
+ continue;
137
+ }
138
+
139
+ if (WIFEXITED(status) || WIFSIGNALED(status)) {
140
+ tracee->dead = 1;
141
+
142
+ if (pt_tracee_wipedoutq(pt->tracee)) {
143
+ return Qnil;
144
+ }
145
+
146
+ continue;
147
+ }
148
+
149
+ if (!(WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)) {
150
+ pt_ptrace_syscall(pid, WSTOPSIG(status));
151
+
152
+ continue;
153
+ }
154
+
155
+ ptrace(PTRACE_GETREGS, pid, 0, &regs);
156
+
157
+ if (regs.orig_rax != SYS_write) {
158
+ pt_ptrace_syscall(pid, 0);
159
+
160
+ continue;
161
+ }
162
+
163
+ syscall = 1 - syscall;
164
+ if (syscall && (pt->fd == 0 || regs.rdi == pt->fd)) {
165
+ string = malloc(regs.rdx + sizeof(long));
166
+
167
+ if (string == NULL) {
168
+ exit(EXIT_FAILURE);
169
+ }
170
+
171
+ pt_read_data(pid, regs.rsi, string, regs.rdx);
172
+
173
+ rb_proc_call(pt->proc, rb_ary_new_from_args(3, INT2NUM((int)pid), INT2NUM(regs.rdi), rb_str_new_cstr(string)));
174
+
175
+ free(string);
176
+ }
177
+
178
+ pt_ptrace_syscall(pid, 0);
179
+ }
180
+
181
+ return Qnil;
182
+ }
183
+
184
+ static VALUE
185
+ pt_finalize(VALUE self)
186
+ {
187
+ pt_process_tail_t *pt;
188
+ Data_Get_Struct(self, pt_process_tail_t, pt);
189
+
190
+ ptrace(PTRACE_DETACH, pt->pid, NULL, NULL);
191
+ pt_tracee_free(&pt->tracee);
192
+
193
+ pt_unlock_trace();
194
+ rb_funcall(pt->parent_thread, rb_intern("raise"), 1, ProcessTail_StopTracing);
195
+
196
+ return Qnil;
197
+ }
198
+
199
+ static VALUE
200
+ pt_loop_thread(void *self)
201
+ {
202
+ pt_lock_trace();
203
+
204
+ rb_ensure(pt_loop, (VALUE)self, pt_finalize, (VALUE)self);
205
+
206
+ return Qnil;
207
+ }
208
+
209
+ static void
210
+ wait_queue_ntimes(VALUE wait_queue, int n)
211
+ {
212
+ int i;
213
+
214
+ for (i = 0; i < n; i++) {
215
+ rb_funcall(wait_queue, rb_intern("deq"), 0);
216
+ }
217
+ }
218
+
219
+ VALUE
220
+ pt_ptrace_attach(VALUE self)
221
+ {
222
+ pt_process_tail_t *pt;
223
+
224
+ Data_Get_Struct(self, pt_process_tail_t, pt);
225
+
226
+ pt->tracee = pt_extract_tracee_list(pt->pid);
227
+ pt->wait_queue = rb_class_new_instance(0, NULL, rb_path2class("Queue"));
228
+ pt->parent_thread = rb_thread_current();
229
+ pt->trace_thread = rb_thread_create(pt_loop_thread, (void *)self);
230
+
231
+ wait_queue_ntimes(pt->wait_queue, pt_tracee_length(pt->tracee));
232
+
233
+ return pt->trace_thread;
234
+ }
@@ -0,0 +1,8 @@
1
+ #ifndef PT_PTRACE_H
2
+ #define PT_PTRACE_H
3
+
4
+ #include <ruby.h>
5
+
6
+ VALUE pt_ptrace_attach(VALUE self);
7
+
8
+ #endif
@@ -0,0 +1,95 @@
1
+ #include <sys/types.h>
2
+ #include <stdlib.h>
3
+ #include "pt_tracee.h"
4
+
5
+ void
6
+ pt_tracee_initialize(pt_tracee_t *tracee)
7
+ {
8
+ tracee->pid = 0;
9
+ tracee->activated = 0;
10
+ tracee->dead = 0;
11
+ tracee->next = NULL;
12
+ }
13
+
14
+ pt_tracee_t *
15
+ pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
16
+ {
17
+ pt_tracee_t *tracee = malloc(sizeof(pt_tracee_t));
18
+
19
+ if (tracee == NULL) {
20
+ exit(EXIT_FAILURE);
21
+ }
22
+
23
+ pt_tracee_initialize(tracee);
24
+ tracee->pid = pid;
25
+ tracee->next = (struct pt_tracee *)*headpp;
26
+
27
+ (*headpp) = tracee;
28
+
29
+ return tracee;
30
+ }
31
+
32
+ pt_tracee_t *
33
+ pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid)
34
+ {
35
+ pt_tracee_t *headp = *headpp;
36
+ pt_tracee_t *current = headp;
37
+
38
+ while (current) {
39
+ if (current->pid == pid) {
40
+ return current;
41
+ }
42
+
43
+ current = current->next;
44
+ }
45
+
46
+ return pt_tracee_add(headpp, pid);
47
+ }
48
+
49
+ int
50
+ pt_tracee_wipedoutq(pt_tracee_t *headp)
51
+ {
52
+ pt_tracee_t *current = headp;
53
+
54
+ while (current) {
55
+ if (current->dead == 0) {
56
+ return 0;
57
+ }
58
+
59
+ current = current->next;
60
+ }
61
+
62
+ return 1;
63
+ }
64
+
65
+ void
66
+ pt_tracee_free(pt_tracee_t **headpp)
67
+ {
68
+ pt_tracee_t *tracee = *headpp;
69
+
70
+ if (tracee) {
71
+ pt_tracee_free(&tracee->next);
72
+
73
+ free(tracee);
74
+ tracee = NULL;
75
+ }
76
+ }
77
+
78
+ int
79
+ pt_tracee_length(pt_tracee_t *tracee)
80
+ {
81
+ pt_tracee_t *current;
82
+ int length = 0;
83
+
84
+ if (tracee == NULL) {
85
+ return length;
86
+ }
87
+
88
+ current = tracee;
89
+
90
+ do {
91
+ length++;
92
+ } while ((current = current->next) != NULL);
93
+
94
+ return length;
95
+ }
@@ -0,0 +1,20 @@
1
+ #ifndef PT_TRACEE_H
2
+ #define PT_TRACEE_H
3
+
4
+ #include <sys/types.h>
5
+
6
+ typedef struct pt_tracee {
7
+ pid_t pid;
8
+ int activated;
9
+ int dead;
10
+ struct pt_tracee *next;
11
+ } pt_tracee_t;
12
+
13
+ void pt_tracee_initialize(pt_tracee_t *tracee);
14
+ pt_tracee_t *pt_tracee_add(pt_tracee_t **headpp, pid_t pid);
15
+ pt_tracee_t *pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid);
16
+ int pt_tracee_wipedoutq(pt_tracee_t *headp);
17
+ void pt_tracee_free(pt_tracee_t **headpp);
18
+ int pt_tracee_length(pt_tracee_t *tracee);
19
+
20
+ #endif
@@ -1,87 +1,107 @@
1
- require 'thread'
2
1
  require 'process_tail/process_tail'
3
2
  require 'process_tail/version'
3
+ require 'erb'
4
+ require 'thread'
4
5
 
5
6
  module ProcessTail
6
- TRACE_LOCK = Mutex.new # NOTE For now, ProcessTail can't trace multiple processes at the same time
7
-
8
- class << self
9
- def open(pid, fd = :stdout)
10
- read_io, write_io = IO.pipe
11
- trace_thread = trace(pid, fd) {|str, *|
12
- unless read_io.closed?
13
- write_io.write str
14
- else
15
- trace_thread.kill
16
- end
17
- }
7
+ module ReadIOExtension
8
+ attr_accessor :process_tail_after_close
9
+
10
+ def close
11
+ super
12
+ ensure
13
+ if hook = process_tail_after_close
14
+ hook.call
15
+ end
16
+ end
17
+ end
18
18
 
19
- wait_thread = fork_wait_thread(trace_thread, [read_io, write_io])
19
+ class Tracer
20
+ def attach
21
+ case
22
+ when respond_to?(:ptrace_attach, true)
23
+ ptrace_attach
24
+ when respond_to?(:dtrace_attach, true)
25
+ dtrace_attach
26
+ else
27
+ raise NotImplementedError
28
+ end
20
29
 
21
- if block_given?
22
- value = yield(read_io)
30
+ self
31
+ end
23
32
 
24
- trace_thread.kill
25
- wait_thread.join
33
+ def wait
34
+ trace_thread.join
26
35
 
27
- value
28
- else
29
- read_io
30
- end
36
+ self
37
+ rescue StopTracing
38
+ # NOP
31
39
  end
32
40
 
33
- def trace(pid, fd = :stdout, &block)
34
- TRACE_LOCK.synchronize {
35
- trace_without_lock(pid, fd, &block)
36
- }
41
+ def detach
42
+ trace_thread.kill if trace_thread.alive?
43
+
44
+ wait
37
45
  end
38
46
 
39
47
  private
40
48
 
41
- def trace_without_lock(pid, fd, &block)
42
- task_ids = extract_task_ids(pid)
43
- wait_queue = Queue.new
44
- trace_thread = Thread.fork {
45
- begin
46
- task_ids.each do |tid|
47
- attach tid
48
- end
49
-
50
- do_trace extract_fd(fd), wait_queue, &block
51
- ensure
52
- task_ids.each do |tid|
53
- detach tid
54
- end
55
- end
56
- }
49
+ def generate_dscript
50
+ template = File.read(File.join(__dir__, 'process_tail/process_tail.d'))
57
51
 
58
- wait_all_attach task_ids, wait_queue
52
+ ERB.new(template).result(binding)
53
+ end
54
+ end
59
55
 
60
- trace_thread
56
+ class << self
57
+ def each(pid)
58
+ tracer = trace(pid, :all) {|tid, fd, str|
59
+ yield tid, fd, str
60
+ }
61
+
62
+ tracer.wait
63
+ rescue StopTracing
64
+ # NOP
65
+ ensure
66
+ tracer.detach
61
67
  end
62
68
 
63
- def fork_wait_thread(trace_thread, pipe)
64
- Thread.fork {
65
- begin
66
- trace_thread.join
67
- ensure
68
- pipe.each do |io|
69
- io.close unless io.closed?
70
- end
69
+ def open(pid, fd = :stdout)
70
+ read_io, write_io = IO.pipe
71
+
72
+ tracer = trace(pid, fd) {|tid, fd_num, str|
73
+ unless read_io.closed?
74
+ write_io.write str
75
+ else
76
+ write_io.close
77
+ process_tail.detach
71
78
  end
72
79
  }
73
- end
74
80
 
75
- def wait_all_attach(task_ids, waitq)
76
- task_ids.size.times do
77
- waitq.deq
81
+ read_io.extend ReadIOExtension
82
+ read_io.process_tail_after_close = -> {
83
+ write_io.close unless write_io.closed?
84
+ tracer.detach
85
+ }
86
+
87
+ block_given? ? yield(read_io) : read_io
88
+ rescue StopTracing
89
+ # NOP
90
+ ensure
91
+ if block_given?
92
+ [read_io, write_io].each do |io|
93
+ io.close unless io.closed?
94
+ end
78
95
  end
79
96
  end
80
97
 
81
- def extract_task_ids(pid)
82
- Dir.open("/proc/#{pid}/task") {|dir|
83
- dir.entries.grep(/^\d+$/).map(&:to_i)
84
- }
98
+ private
99
+
100
+ def trace(pid, fd, &block)
101
+ pt = Tracer.new(pid, extract_fd(fd), &block)
102
+ pt.attach
103
+
104
+ pt
85
105
  end
86
106
 
87
107
  def extract_fd(name)
@@ -0,0 +1,77 @@
1
+ inline int PID = <%= pid %>;
2
+ inline int FD = <%= fd %>;
3
+
4
+ dtrace:::BEGIN
5
+ {
6
+ processes[pid] = 0;
7
+ self->child = 0;
8
+ }
9
+
10
+ syscall:::entry
11
+ /processes[ppid] == -1 && self->child == 0/
12
+ {
13
+ self->child = 1;
14
+ }
15
+
16
+ syscall:::entry
17
+ /processes[ppid] > 0 && self->child == 0/
18
+ {
19
+ this->vforking_tid = processes[ppid];
20
+ self->child = (this->vforking_tid == tid) ? 0 : 1;
21
+ }
22
+
23
+ syscall:::entry
24
+ /pid == PID || self->child/
25
+ {
26
+ self->tracked = 1;
27
+ self->arg0 = arg0;
28
+ self->arg1 = arg1;
29
+ self->arg2 = arg2;
30
+ }
31
+
32
+ syscall::fork:entry
33
+ /self->tracked/
34
+ {
35
+ processes[pid] = -1;
36
+ }
37
+
38
+ syscall::vfork:entry
39
+ /self->tracked/
40
+ {
41
+ processes[pid] = tid;
42
+ }
43
+
44
+ syscall::exit:entry
45
+ {
46
+ self->child = 0;
47
+ processes[pid] = 0;
48
+ }
49
+
50
+ syscall::exit:entry
51
+ /pid == PID/
52
+ {
53
+ exit(0);
54
+ }
55
+
56
+ syscall:::entry
57
+ /!((probefunc == "write" || probefunc == "write_nocancel") && (FD == 0 || self->arg0 == FD))/
58
+ {
59
+ self->tracked = 0;
60
+ self->arg0 = 0;
61
+ self->arg1 = 0;
62
+ self->arg2 = 0;
63
+ }
64
+
65
+ syscall::write:return,
66
+ syscall::write_nocancel:return
67
+ /self->tracked/
68
+ {
69
+ trace(pid); /* pid */
70
+ trace(self->arg0); /* fd */
71
+ trace(stringof(copyin(self->arg1, arg0))); /* content */
72
+
73
+ self->tracked = 0;
74
+ self->arg0 = 0;
75
+ self->arg1 = 0;
76
+ self->arg2 = 0;
77
+ }
@@ -1,3 +1,3 @@
1
1
  module ProcessTail
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_tail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hika Hibariya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-15 00:00:00.000000000 Z
11
+ date: 2015-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,7 +84,15 @@ files:
84
84
  - bin/process_tail
85
85
  - ext/process_tail/extconf.rb
86
86
  - ext/process_tail/process_tail.c
87
+ - ext/process_tail/process_tail.h
88
+ - ext/process_tail/pt_dtrace.c
89
+ - ext/process_tail/pt_dtrace.h
90
+ - ext/process_tail/pt_ptrace.c
91
+ - ext/process_tail/pt_ptrace.h
92
+ - ext/process_tail/pt_tracee.c
93
+ - ext/process_tail/pt_tracee.h
87
94
  - lib/process_tail.rb
95
+ - lib/process_tail/process_tail.d
88
96
  - lib/process_tail/version.rb
89
97
  - process_tail.gemspec
90
98
  - spec/process_tail_spec.rb