process_tail 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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