lldb 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE-APACHE +190 -0
- data/LICENSE-MIT +21 -0
- data/README.md +240 -0
- data/Rakefile +80 -0
- data/Steepfile +11 -0
- data/examples/basic_debug.rb +111 -0
- data/examples/breakpoints.rb +72 -0
- data/examples/expression_eval.rb +84 -0
- data/examples/test_program.c +14 -0
- data/ext/lldb/Makefile +24 -0
- data/ext/lldb/extconf.rb +160 -0
- data/ext/lldb/liblldb_wrapper.dylib +0 -0
- data/ext/lldb/lldb_wrapper.cpp +2051 -0
- data/ext/lldb/lldb_wrapper.h +424 -0
- data/ext/lldb/lldb_wrapper.o +0 -0
- data/ext/lldb/mkmf.log +24 -0
- data/lib/lldb/breakpoint.rb +233 -0
- data/lib/lldb/breakpoint_location.rb +117 -0
- data/lib/lldb/command_interpreter.rb +62 -0
- data/lib/lldb/command_return_object.rb +71 -0
- data/lib/lldb/debugger.rb +179 -0
- data/lib/lldb/error.rb +70 -0
- data/lib/lldb/ffi_bindings.rb +394 -0
- data/lib/lldb/frame.rb +226 -0
- data/lib/lldb/launch_info.rb +85 -0
- data/lib/lldb/module.rb +61 -0
- data/lib/lldb/process.rb +317 -0
- data/lib/lldb/symbol_context.rb +52 -0
- data/lib/lldb/target.rb +427 -0
- data/lib/lldb/thread.rb +226 -0
- data/lib/lldb/type.rb +215 -0
- data/lib/lldb/types.rb +190 -0
- data/lib/lldb/value.rb +334 -0
- data/lib/lldb/value_list.rb +88 -0
- data/lib/lldb/version.rb +7 -0
- data/lib/lldb/watchpoint.rb +145 -0
- data/lib/lldb.rb +64 -0
- data/lldb.gemspec +33 -0
- data/rbs_collection.lock.yaml +220 -0
- data/rbs_collection.yaml +19 -0
- data/sig/lldb/ffi_bindings.rbs +312 -0
- metadata +102 -0
data/lib/lldb/target.rb
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
module LLDB
|
|
6
|
+
class Target
|
|
7
|
+
# @rbs return: Debugger
|
|
8
|
+
attr_reader :debugger
|
|
9
|
+
|
|
10
|
+
# @rbs ptr: FFI::Pointer
|
|
11
|
+
# @rbs debugger: Debugger
|
|
12
|
+
# @rbs return: void
|
|
13
|
+
def initialize(ptr, debugger:)
|
|
14
|
+
@ptr = ptr # : FFI::Pointer
|
|
15
|
+
@debugger = debugger
|
|
16
|
+
@breakpoints = [] # : Array[Breakpoint]
|
|
17
|
+
@watchpoints = [] # : Array[Watchpoint]
|
|
18
|
+
@process = nil # : Process?
|
|
19
|
+
ObjectSpace.define_finalizer(self, self.class.release(@ptr))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @rbs ptr: FFI::Pointer
|
|
23
|
+
# @rbs return: ^(Integer) -> void
|
|
24
|
+
def self.release(ptr)
|
|
25
|
+
->(_id) { FFIBindings.lldb_target_destroy(ptr) unless ptr.null? }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs return: bool
|
|
29
|
+
def valid?
|
|
30
|
+
!@ptr.null? && FFIBindings.lldb_target_is_valid(@ptr) != 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs args: Array[String]?
|
|
34
|
+
# @rbs env: Hash[String, String]?
|
|
35
|
+
# @rbs working_dir: String?
|
|
36
|
+
# @rbs return: Process
|
|
37
|
+
def launch(args: nil, env: nil, working_dir: nil)
|
|
38
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
39
|
+
|
|
40
|
+
# Use LaunchInfo with STOP_AT_ENTRY flag for reliable cross-platform behavior
|
|
41
|
+
launch_info = LaunchInfo.new(args)
|
|
42
|
+
launch_info.launch_flags = LaunchFlags::STOP_AT_ENTRY
|
|
43
|
+
launch_info.working_directory = working_dir if working_dir
|
|
44
|
+
launch_info.set_environment(env) if env
|
|
45
|
+
|
|
46
|
+
error = Error.new
|
|
47
|
+
process_ptr = FFIBindings.lldb_target_launch(@ptr, launch_info.to_ptr, error.to_ptr)
|
|
48
|
+
|
|
49
|
+
error.raise_if_error!
|
|
50
|
+
raise LaunchError, 'Failed to launch process' if process_ptr.nil? || process_ptr.null?
|
|
51
|
+
|
|
52
|
+
@process = Process.new(process_ptr, target: self)
|
|
53
|
+
|
|
54
|
+
# Wait for process to stop when in synchronous mode
|
|
55
|
+
# On some platforms (Linux), the process may not be immediately stopped
|
|
56
|
+
unless @debugger.async?
|
|
57
|
+
wait_for_process_stop(@process)
|
|
58
|
+
|
|
59
|
+
# STOP_AT_ENTRY stops at the program entry point, not at breakpoints.
|
|
60
|
+
# If there are breakpoints set, continue to let the process run to the first breakpoint.
|
|
61
|
+
if num_breakpoints > 0 && @process.state == State::STOPPED && @process.valid?
|
|
62
|
+
@process.continue
|
|
63
|
+
wait_for_process_stop(@process)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@process
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @rbs launch_info: LaunchInfo
|
|
71
|
+
# @rbs return: Process
|
|
72
|
+
def launch_with_info(launch_info)
|
|
73
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
74
|
+
raise ArgumentError, 'launch_info is required' unless launch_info
|
|
75
|
+
|
|
76
|
+
error = Error.new
|
|
77
|
+
process_ptr = FFIBindings.lldb_target_launch(@ptr, launch_info.to_ptr, error.to_ptr)
|
|
78
|
+
|
|
79
|
+
error.raise_if_error!
|
|
80
|
+
raise LaunchError, 'Failed to launch process' if process_ptr.nil? || process_ptr.null?
|
|
81
|
+
|
|
82
|
+
@process = Process.new(process_ptr, target: self)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs pid: Integer
|
|
86
|
+
# @rbs return: Process
|
|
87
|
+
def attach(pid:)
|
|
88
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
89
|
+
|
|
90
|
+
error = Error.new
|
|
91
|
+
process_ptr = FFIBindings.lldb_target_attach_to_process_with_id(@ptr, pid, error.to_ptr)
|
|
92
|
+
|
|
93
|
+
error.raise_if_error!
|
|
94
|
+
raise AttachError, "Failed to attach to process #{pid}" if process_ptr.nil? || process_ptr.null?
|
|
95
|
+
|
|
96
|
+
@process = Process.new(process_ptr, target: self)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @rbs symbol_name: String
|
|
100
|
+
# @rbs module_name: String?
|
|
101
|
+
# @rbs return: Breakpoint
|
|
102
|
+
def breakpoint_create_by_name(symbol_name, module_name: nil)
|
|
103
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
104
|
+
|
|
105
|
+
bp_ptr = FFIBindings.lldb_target_breakpoint_create_by_name(@ptr, symbol_name, module_name)
|
|
106
|
+
raise BreakpointError, "Failed to create breakpoint for '#{symbol_name}'" if bp_ptr.nil? || bp_ptr.null?
|
|
107
|
+
|
|
108
|
+
bp = Breakpoint.new(bp_ptr, target: self)
|
|
109
|
+
@breakpoints << bp
|
|
110
|
+
bp
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @rbs file: String
|
|
114
|
+
# @rbs line: Integer
|
|
115
|
+
# @rbs return: Breakpoint
|
|
116
|
+
def breakpoint_create_by_location(file, line)
|
|
117
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
118
|
+
|
|
119
|
+
bp_ptr = FFIBindings.lldb_target_breakpoint_create_by_location(@ptr, file, line)
|
|
120
|
+
raise BreakpointError, "Failed to create breakpoint at #{file}:#{line}" if bp_ptr.nil? || bp_ptr.null?
|
|
121
|
+
|
|
122
|
+
bp = Breakpoint.new(bp_ptr, target: self)
|
|
123
|
+
@breakpoints << bp
|
|
124
|
+
bp
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @rbs address: Integer
|
|
128
|
+
# @rbs return: Breakpoint
|
|
129
|
+
def breakpoint_create_by_address(address)
|
|
130
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
131
|
+
|
|
132
|
+
bp_ptr = FFIBindings.lldb_target_breakpoint_create_by_address(@ptr, address)
|
|
133
|
+
if bp_ptr.nil? || bp_ptr.null?
|
|
134
|
+
raise BreakpointError,
|
|
135
|
+
"Failed to create breakpoint at address 0x#{address.to_s(16)}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
bp = Breakpoint.new(bp_ptr, target: self)
|
|
139
|
+
@breakpoints << bp
|
|
140
|
+
bp
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs breakpoint_id: Integer
|
|
144
|
+
# @rbs return: bool
|
|
145
|
+
def delete_breakpoint(breakpoint_id)
|
|
146
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
147
|
+
|
|
148
|
+
result = FFIBindings.lldb_target_delete_breakpoint(@ptr, breakpoint_id)
|
|
149
|
+
@breakpoints.reject! { |bp| bp.id == breakpoint_id } if result != 0
|
|
150
|
+
result != 0
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs id: Integer
|
|
154
|
+
# @rbs return: Breakpoint?
|
|
155
|
+
def find_breakpoint_by_id(id)
|
|
156
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
157
|
+
|
|
158
|
+
bp_ptr = FFIBindings.lldb_target_find_breakpoint_by_id(@ptr, id)
|
|
159
|
+
return nil if bp_ptr.nil? || bp_ptr.null?
|
|
160
|
+
|
|
161
|
+
Breakpoint.new(bp_ptr, target: self)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @rbs return: Integer
|
|
165
|
+
def num_breakpoints
|
|
166
|
+
return 0 unless valid?
|
|
167
|
+
|
|
168
|
+
FFIBindings.lldb_target_get_num_breakpoints(@ptr)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @rbs index: Integer
|
|
172
|
+
# @rbs return: Breakpoint?
|
|
173
|
+
def breakpoint_at_index(index)
|
|
174
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
175
|
+
|
|
176
|
+
bp_ptr = FFIBindings.lldb_target_get_breakpoint_at_index(@ptr, index)
|
|
177
|
+
return nil if bp_ptr.nil? || bp_ptr.null?
|
|
178
|
+
|
|
179
|
+
Breakpoint.new(bp_ptr, target: self)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @rbs return: Array[Breakpoint]
|
|
183
|
+
def breakpoints
|
|
184
|
+
(0...num_breakpoints).map { |i| breakpoint_at_index(i) }.compact
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @rbs return: Process?
|
|
188
|
+
def process
|
|
189
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
190
|
+
|
|
191
|
+
process_ptr = FFIBindings.lldb_target_get_process(@ptr)
|
|
192
|
+
return nil if process_ptr.nil? || process_ptr.null?
|
|
193
|
+
|
|
194
|
+
Process.new(process_ptr, target: self)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @rbs return: String?
|
|
198
|
+
def executable_path
|
|
199
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
200
|
+
|
|
201
|
+
FFIBindings.lldb_target_get_executable_path(@ptr)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @rbs return: Integer
|
|
205
|
+
def num_modules
|
|
206
|
+
return 0 unless valid?
|
|
207
|
+
|
|
208
|
+
FFIBindings.lldb_target_get_num_modules(@ptr)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# @rbs index: Integer
|
|
212
|
+
# @rbs return: Module?
|
|
213
|
+
def module_at_index(index)
|
|
214
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
215
|
+
|
|
216
|
+
mod_ptr = FFIBindings.lldb_target_get_module_at_index(@ptr, index)
|
|
217
|
+
return nil if mod_ptr.nil? || mod_ptr.null?
|
|
218
|
+
|
|
219
|
+
Module.new(mod_ptr, target: self)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# @rbs return: Array[Module]
|
|
223
|
+
def modules
|
|
224
|
+
(0...num_modules).map { |i| module_at_index(i) }.compact
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @rbs name: String
|
|
228
|
+
# @rbs wait_for: bool
|
|
229
|
+
# @rbs return: Process
|
|
230
|
+
def attach_with_name(name, wait_for: false)
|
|
231
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
232
|
+
|
|
233
|
+
error = Error.new
|
|
234
|
+
process_ptr = FFIBindings.lldb_target_attach_to_process_with_name(@ptr, name, wait_for ? 1 : 0, error.to_ptr)
|
|
235
|
+
|
|
236
|
+
error.raise_if_error!
|
|
237
|
+
raise AttachError, "Failed to attach to process '#{name}'" if process_ptr.nil? || process_ptr.null?
|
|
238
|
+
|
|
239
|
+
@process = Process.new(process_ptr, target: self)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @rbs regex: String
|
|
243
|
+
# @rbs module_name: String?
|
|
244
|
+
# @rbs return: Breakpoint
|
|
245
|
+
def breakpoint_create_by_regex(regex, module_name: nil)
|
|
246
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
247
|
+
|
|
248
|
+
bp_ptr = FFIBindings.lldb_target_breakpoint_create_by_regex(@ptr, regex, module_name)
|
|
249
|
+
raise BreakpointError, "Failed to create breakpoint for regex '#{regex}'" if bp_ptr.nil? || bp_ptr.null?
|
|
250
|
+
|
|
251
|
+
bp = Breakpoint.new(bp_ptr, target: self)
|
|
252
|
+
@breakpoints << bp
|
|
253
|
+
bp
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# @rbs regex: String
|
|
257
|
+
# @rbs source_file: String?
|
|
258
|
+
# @rbs return: Breakpoint
|
|
259
|
+
def breakpoint_create_by_source_regex(regex, source_file: nil)
|
|
260
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
261
|
+
|
|
262
|
+
bp_ptr = FFIBindings.lldb_target_breakpoint_create_by_source_regex(@ptr, regex, source_file)
|
|
263
|
+
raise BreakpointError, "Failed to create breakpoint for source regex '#{regex}'" if bp_ptr.nil? || bp_ptr.null?
|
|
264
|
+
|
|
265
|
+
bp = Breakpoint.new(bp_ptr, target: self)
|
|
266
|
+
@breakpoints << bp
|
|
267
|
+
bp
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# @rbs return: bool
|
|
271
|
+
def delete_all_breakpoints
|
|
272
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
273
|
+
|
|
274
|
+
result = FFIBindings.lldb_target_delete_all_breakpoints(@ptr) != 0
|
|
275
|
+
@breakpoints.clear if result
|
|
276
|
+
result
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# @rbs return: bool
|
|
280
|
+
def enable_all_breakpoints
|
|
281
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
282
|
+
|
|
283
|
+
FFIBindings.lldb_target_enable_all_breakpoints(@ptr) != 0
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @rbs return: bool
|
|
287
|
+
def disable_all_breakpoints
|
|
288
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
289
|
+
|
|
290
|
+
FFIBindings.lldb_target_disable_all_breakpoints(@ptr) != 0
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# @rbs expression: String
|
|
294
|
+
# @rbs return: Value?
|
|
295
|
+
def evaluate_expression(expression)
|
|
296
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
297
|
+
|
|
298
|
+
value_ptr = FFIBindings.lldb_target_evaluate_expression(@ptr, expression)
|
|
299
|
+
return nil if value_ptr.nil? || value_ptr.null?
|
|
300
|
+
|
|
301
|
+
Value.new(value_ptr, parent: self)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# @rbs address: Integer
|
|
305
|
+
# @rbs size: Integer
|
|
306
|
+
# @rbs return: String
|
|
307
|
+
def read_memory(address, size)
|
|
308
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
309
|
+
|
|
310
|
+
buffer = FFI::MemoryPointer.new(:uint8, size)
|
|
311
|
+
error = Error.new
|
|
312
|
+
bytes_read = FFIBindings.lldb_target_read_memory(@ptr, address, buffer, size, error.to_ptr)
|
|
313
|
+
|
|
314
|
+
error.raise_if_error!
|
|
315
|
+
buffer.read_string(bytes_read)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# @rbs return: Integer
|
|
319
|
+
def address_byte_size
|
|
320
|
+
return 0 unless valid?
|
|
321
|
+
|
|
322
|
+
FFIBindings.lldb_target_get_address_byte_size(@ptr)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @rbs return: String?
|
|
326
|
+
def triple
|
|
327
|
+
return nil unless valid?
|
|
328
|
+
|
|
329
|
+
FFIBindings.lldb_target_get_triple(@ptr)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# @rbs address: Integer
|
|
333
|
+
# @rbs size: Integer
|
|
334
|
+
# @rbs read: bool
|
|
335
|
+
# @rbs write: bool
|
|
336
|
+
# @rbs return: Watchpoint
|
|
337
|
+
def watch_address(address, size, read: false, write: true)
|
|
338
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
339
|
+
|
|
340
|
+
error = Error.new
|
|
341
|
+
wp_ptr = FFIBindings.lldb_target_watch_address(@ptr, address, size, read ? 1 : 0, write ? 1 : 0, error.to_ptr)
|
|
342
|
+
|
|
343
|
+
error.raise_if_error!
|
|
344
|
+
raise LLDBError, "Failed to create watchpoint at address 0x#{address.to_s(16)}" if wp_ptr.nil? || wp_ptr.null?
|
|
345
|
+
|
|
346
|
+
wp = Watchpoint.new(wp_ptr, target: self)
|
|
347
|
+
@watchpoints << wp
|
|
348
|
+
wp
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# @rbs watchpoint_id: Integer
|
|
352
|
+
# @rbs return: bool
|
|
353
|
+
def delete_watchpoint(watchpoint_id)
|
|
354
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
355
|
+
|
|
356
|
+
result = FFIBindings.lldb_target_delete_watchpoint(@ptr, watchpoint_id)
|
|
357
|
+
@watchpoints.reject! { |wp| wp.id == watchpoint_id } if result != 0
|
|
358
|
+
result != 0
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# @rbs return: bool
|
|
362
|
+
def delete_all_watchpoints
|
|
363
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
364
|
+
|
|
365
|
+
result = FFIBindings.lldb_target_delete_all_watchpoints(@ptr) != 0
|
|
366
|
+
@watchpoints.clear if result
|
|
367
|
+
result
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# @rbs id: Integer
|
|
371
|
+
# @rbs return: Watchpoint?
|
|
372
|
+
def find_watchpoint_by_id(id)
|
|
373
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
374
|
+
|
|
375
|
+
wp_ptr = FFIBindings.lldb_target_find_watchpoint_by_id(@ptr, id)
|
|
376
|
+
return nil if wp_ptr.nil? || wp_ptr.null?
|
|
377
|
+
|
|
378
|
+
Watchpoint.new(wp_ptr, target: self)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# @rbs return: Integer
|
|
382
|
+
def num_watchpoints
|
|
383
|
+
return 0 unless valid?
|
|
384
|
+
|
|
385
|
+
FFIBindings.lldb_target_get_num_watchpoints(@ptr)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# @rbs index: Integer
|
|
389
|
+
# @rbs return: Watchpoint?
|
|
390
|
+
def watchpoint_at_index(index)
|
|
391
|
+
raise InvalidObjectError, 'Target is not valid' unless valid?
|
|
392
|
+
|
|
393
|
+
wp_ptr = FFIBindings.lldb_target_get_watchpoint_at_index(@ptr, index)
|
|
394
|
+
return nil if wp_ptr.nil? || wp_ptr.null?
|
|
395
|
+
|
|
396
|
+
Watchpoint.new(wp_ptr, target: self)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# @rbs return: Array[Watchpoint]
|
|
400
|
+
def watchpoints
|
|
401
|
+
(0...num_watchpoints).map { |i| watchpoint_at_index(i) }.compact
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# @rbs return: FFI::Pointer
|
|
405
|
+
def to_ptr
|
|
406
|
+
@ptr
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
private
|
|
410
|
+
|
|
411
|
+
# @rbs process: Process
|
|
412
|
+
# @rbs timeout: Float
|
|
413
|
+
# @rbs return: void
|
|
414
|
+
def wait_for_process_stop(process, timeout: 10.0)
|
|
415
|
+
require 'timeout'
|
|
416
|
+
|
|
417
|
+
Timeout.timeout(timeout, LaunchError, "Process failed to stop within #{timeout} seconds") do
|
|
418
|
+
loop do
|
|
419
|
+
state = process.state
|
|
420
|
+
break if state == State::STOPPED || state == State::EXITED || state == State::CRASHED
|
|
421
|
+
|
|
422
|
+
sleep(0.01)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
data/lib/lldb/thread.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
module LLDB
|
|
6
|
+
class Thread
|
|
7
|
+
# @rbs return: Process
|
|
8
|
+
attr_reader :process
|
|
9
|
+
|
|
10
|
+
# @rbs ptr: FFI::Pointer
|
|
11
|
+
# @rbs process: Process
|
|
12
|
+
# @rbs return: void
|
|
13
|
+
def initialize(ptr, process:)
|
|
14
|
+
@ptr = ptr # : FFI::Pointer
|
|
15
|
+
@process = process
|
|
16
|
+
ObjectSpace.define_finalizer(self, self.class.release(@ptr))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs ptr: FFI::Pointer
|
|
20
|
+
# @rbs return: ^(Integer) -> void
|
|
21
|
+
def self.release(ptr)
|
|
22
|
+
->(_id) { FFIBindings.lldb_thread_destroy(ptr) unless ptr.null? }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs return: bool
|
|
26
|
+
def valid?
|
|
27
|
+
!@ptr.null? && FFIBindings.lldb_thread_is_valid(@ptr) != 0
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @rbs return: bool
|
|
31
|
+
def step_over
|
|
32
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
33
|
+
|
|
34
|
+
FFIBindings.lldb_thread_step_over(@ptr) != 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs return: bool
|
|
38
|
+
def step_into
|
|
39
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
40
|
+
|
|
41
|
+
FFIBindings.lldb_thread_step_into(@ptr) != 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @rbs return: bool
|
|
45
|
+
def step_out
|
|
46
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
47
|
+
|
|
48
|
+
FFIBindings.lldb_thread_step_out(@ptr) != 0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs step_over: bool
|
|
52
|
+
# @rbs return: bool
|
|
53
|
+
def step_instruction(step_over: false)
|
|
54
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
55
|
+
|
|
56
|
+
FFIBindings.lldb_thread_step_instruction(@ptr, step_over ? 1 : 0) != 0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @rbs return: Integer
|
|
60
|
+
def num_frames
|
|
61
|
+
return 0 unless valid?
|
|
62
|
+
|
|
63
|
+
FFIBindings.lldb_thread_get_num_frames(@ptr)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @rbs index: Integer
|
|
67
|
+
# @rbs return: Frame?
|
|
68
|
+
def frame_at_index(index)
|
|
69
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
70
|
+
|
|
71
|
+
frame_ptr = FFIBindings.lldb_thread_get_frame_at_index(@ptr, index)
|
|
72
|
+
return nil if frame_ptr.nil? || frame_ptr.null?
|
|
73
|
+
|
|
74
|
+
Frame.new(frame_ptr, thread: self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs return: Frame?
|
|
78
|
+
def selected_frame
|
|
79
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
80
|
+
|
|
81
|
+
frame_ptr = FFIBindings.lldb_thread_get_selected_frame(@ptr)
|
|
82
|
+
return nil if frame_ptr.nil? || frame_ptr.null?
|
|
83
|
+
|
|
84
|
+
Frame.new(frame_ptr, thread: self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @rbs index: Integer
|
|
88
|
+
# @rbs return: bool
|
|
89
|
+
def select_frame(index)
|
|
90
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
91
|
+
|
|
92
|
+
FFIBindings.lldb_thread_set_selected_frame(@ptr, index) != 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @rbs return: Array[Frame]
|
|
96
|
+
def frames
|
|
97
|
+
(0...num_frames).map { |i| frame_at_index(i) }.compact
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @rbs return: Integer
|
|
101
|
+
def thread_id
|
|
102
|
+
return 0 unless valid?
|
|
103
|
+
|
|
104
|
+
FFIBindings.lldb_thread_get_thread_id(@ptr)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
alias id thread_id
|
|
108
|
+
|
|
109
|
+
# @rbs return: String?
|
|
110
|
+
def name
|
|
111
|
+
return nil unless valid?
|
|
112
|
+
|
|
113
|
+
FFIBindings.lldb_thread_get_name(@ptr)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @rbs return: Integer
|
|
117
|
+
def stop_reason
|
|
118
|
+
return StopReason::INVALID unless valid?
|
|
119
|
+
|
|
120
|
+
FFIBindings.lldb_thread_get_stop_reason(@ptr)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @rbs return: String
|
|
124
|
+
def stop_reason_name
|
|
125
|
+
StopReason.name(stop_reason)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @rbs return: bool
|
|
129
|
+
def stopped_at_breakpoint?
|
|
130
|
+
stop_reason == StopReason::BREAKPOINT
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @rbs return: Integer
|
|
134
|
+
def stop_reason_data_count
|
|
135
|
+
return 0 unless valid?
|
|
136
|
+
|
|
137
|
+
FFIBindings.lldb_thread_get_stop_reason_data_count(@ptr)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @rbs index: Integer
|
|
141
|
+
# @rbs return: Integer
|
|
142
|
+
def stop_reason_data_at_index(index)
|
|
143
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
144
|
+
|
|
145
|
+
FFIBindings.lldb_thread_get_stop_reason_data_at_index(@ptr, index)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs return: Array[Integer]
|
|
149
|
+
def stop_reason_data
|
|
150
|
+
(0...stop_reason_data_count).map { |i| stop_reason_data_at_index(i) }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs address: Integer
|
|
154
|
+
# @rbs return: bool
|
|
155
|
+
def run_to_address(address)
|
|
156
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
157
|
+
|
|
158
|
+
FFIBindings.lldb_thread_run_to_address(@ptr, address) != 0
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @rbs return: Integer
|
|
162
|
+
def index_id
|
|
163
|
+
return 0 unless valid?
|
|
164
|
+
|
|
165
|
+
FFIBindings.lldb_thread_get_index_id(@ptr)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @rbs return: String?
|
|
169
|
+
def queue_name
|
|
170
|
+
return nil unless valid?
|
|
171
|
+
|
|
172
|
+
FFIBindings.lldb_thread_get_queue_name(@ptr)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @rbs max_size: Integer
|
|
176
|
+
# @rbs return: String?
|
|
177
|
+
def stop_description(max_size = 256)
|
|
178
|
+
return nil unless valid?
|
|
179
|
+
|
|
180
|
+
FFIBindings.lldb_thread_get_stop_description(@ptr, max_size)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @rbs return: bool
|
|
184
|
+
def stopped?
|
|
185
|
+
return false unless valid?
|
|
186
|
+
|
|
187
|
+
FFIBindings.lldb_thread_is_stopped(@ptr) != 0
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @rbs return: bool
|
|
191
|
+
def suspended?
|
|
192
|
+
return false unless valid?
|
|
193
|
+
|
|
194
|
+
FFIBindings.lldb_thread_is_suspended(@ptr) != 0
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @rbs return: bool
|
|
198
|
+
def suspend
|
|
199
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
200
|
+
|
|
201
|
+
FFIBindings.lldb_thread_suspend(@ptr) != 0
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @rbs return: bool
|
|
205
|
+
def resume
|
|
206
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
207
|
+
|
|
208
|
+
FFIBindings.lldb_thread_resume(@ptr) != 0
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# @rbs return: Process?
|
|
212
|
+
def get_process
|
|
213
|
+
raise InvalidObjectError, 'Thread is not valid' unless valid?
|
|
214
|
+
|
|
215
|
+
process_ptr = FFIBindings.lldb_thread_get_process(@ptr)
|
|
216
|
+
return nil if process_ptr.nil? || process_ptr.null?
|
|
217
|
+
|
|
218
|
+
Process.new(process_ptr, target: @process.target)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @rbs return: FFI::Pointer
|
|
222
|
+
def to_ptr
|
|
223
|
+
@ptr
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|