lldb 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +43 -4
- data/README.md +86 -2
- data/Rakefile +36 -0
- data/bindings/constants.yml +137 -0
- data/bindings/surface.yml +3072 -0
- data/ext/lldb/discovery.rb +101 -0
- data/ext/lldb/extconf.rb +206 -88
- data/ext/lldb/lldb_wrapper.cpp +7934 -877
- data/ext/lldb/lldb_wrapper.h +596 -333
- data/lib/lldb/address.rb +81 -0
- data/lib/lldb/api_support.rb +23 -6
- data/lib/lldb/attach_info.rb +122 -0
- data/lib/lldb/block.rb +121 -0
- data/lib/lldb/breakpoint.rb +11 -6
- data/lib/lldb/breakpoint_location.rb +20 -5
- data/lib/lldb/broadcaster.rb +79 -0
- data/lib/lldb/command_interpreter.rb +9 -4
- data/lib/lldb/command_return_object.rb +24 -5
- data/lib/lldb/compile_unit.rb +64 -0
- data/lib/lldb/context.rb +81 -0
- data/lib/lldb/debugger.rb +55 -12
- data/lib/lldb/error.rb +50 -3
- data/lib/lldb/event.rb +109 -0
- data/lib/lldb/expression_options.rb +124 -0
- data/lib/lldb/ffi_bindings.rb +313 -31
- data/lib/lldb/file_spec.rb +90 -0
- data/lib/lldb/file_spec_list.rb +78 -0
- data/lib/lldb/frame.rb +109 -17
- data/lib/lldb/function.rb +121 -0
- data/lib/lldb/instruction.rb +83 -0
- data/lib/lldb/instruction_list.rb +68 -0
- data/lib/lldb/launch_info.rb +145 -19
- data/lib/lldb/line_entry.rb +75 -0
- data/lib/lldb/listener.rb +96 -0
- data/lib/lldb/memory_region_info.rb +8 -3
- data/lib/lldb/module.rb +46 -5
- data/lib/lldb/native.rb +36 -0
- data/lib/lldb/native_buffer.rb +32 -0
- data/lib/lldb/native_handle.rb +60 -0
- data/lib/lldb/native_lifecycle.rb +64 -0
- data/lib/lldb/native_string_array.rb +34 -0
- data/lib/lldb/process.rb +102 -40
- data/lib/lldb/symbol.rb +117 -0
- data/lib/lldb/symbol_context.rb +9 -4
- data/lib/lldb/target.rb +69 -67
- data/lib/lldb/thread.rb +51 -18
- data/lib/lldb/type.rb +40 -3
- data/lib/lldb/type_member.rb +67 -0
- data/lib/lldb/types.rb +69 -4
- data/lib/lldb/value.rb +27 -17
- data/lib/lldb/value_list.rb +9 -5
- data/lib/lldb/version.rb +1 -1
- data/lib/lldb/watchpoint.rb +26 -9
- data/lib/lldb.rb +78 -10
- data/lldb.gemspec +4 -1
- data/rbs_collection.yaml +1 -1
- data/script/check_bindings +259 -0
- data/script/guard_c_exports +139 -0
- data/sig/lldb/ffi_bindings.rbs +247 -21
- data/sig/lldb/weak_ref.rbs +7 -0
- metadata +30 -6
- data/ext/lldb/Makefile +0 -24
- data/ext/lldb/liblldb_wrapper.dylib +0 -0
- data/ext/lldb/lldb_wrapper.o +0 -0
- data/ext/lldb/mkmf.log +0 -24
data/lib/lldb/value.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
module LLDB
|
|
6
6
|
class Value
|
|
7
|
+
prepend NativeLifecycle
|
|
7
8
|
include Enumerable #[Value]
|
|
8
9
|
|
|
9
10
|
# @rbs return: Frame | Value | Target
|
|
@@ -12,10 +13,13 @@ module LLDB
|
|
|
12
13
|
# @rbs ptr: FFI::Pointer
|
|
13
14
|
# @rbs parent: Frame | Value | Target
|
|
14
15
|
# @rbs return: void
|
|
15
|
-
def initialize(ptr, parent:)
|
|
16
|
-
@ptr = ptr # : FFI::Pointer
|
|
16
|
+
def initialize(ptr, parent:, context: parent.context)
|
|
17
17
|
@parent = parent
|
|
18
|
-
|
|
18
|
+
initialize_native_object(
|
|
19
|
+
ptr,
|
|
20
|
+
release: ->(released) { FFIBindings.lldb_value_destroy(released) },
|
|
21
|
+
context: context
|
|
22
|
+
)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
# @rbs ptr: FFI::Pointer
|
|
@@ -72,7 +76,7 @@ module LLDB
|
|
|
72
76
|
child_ptr = FFIBindings.lldb_value_get_child_at_index(@ptr, index)
|
|
73
77
|
return nil if child_ptr.nil? || child_ptr.null?
|
|
74
78
|
|
|
75
|
-
Value.new(child_ptr, parent: self)
|
|
79
|
+
Value.new(child_ptr, parent: self, context: context)
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
# @rbs name: String
|
|
@@ -83,7 +87,7 @@ module LLDB
|
|
|
83
87
|
child_ptr = FFIBindings.lldb_value_get_child_member_with_name(@ptr, name)
|
|
84
88
|
return nil if child_ptr.nil? || child_ptr.null?
|
|
85
89
|
|
|
86
|
-
Value.new(child_ptr, parent: self)
|
|
90
|
+
Value.new(child_ptr, parent: self, context: context)
|
|
87
91
|
end
|
|
88
92
|
|
|
89
93
|
alias [] child_member
|
|
@@ -105,7 +109,10 @@ module LLDB
|
|
|
105
109
|
def value_as_signed
|
|
106
110
|
return 0 unless valid?
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
error = Error.new
|
|
113
|
+
result = FFIBindings.lldb_value_get_value_as_signed(@ptr, error.to_ptr, 0)
|
|
114
|
+
error.raise_if_error!('value.value_as_signed')
|
|
115
|
+
result
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
alias to_i value_as_signed
|
|
@@ -114,7 +121,10 @@ module LLDB
|
|
|
114
121
|
def value_as_unsigned
|
|
115
122
|
return 0 unless valid?
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
error = Error.new
|
|
125
|
+
result = FFIBindings.lldb_value_get_value_as_unsigned(@ptr, error.to_ptr, 0)
|
|
126
|
+
error.raise_if_error!('value.value_as_unsigned')
|
|
127
|
+
result
|
|
118
128
|
end
|
|
119
129
|
|
|
120
130
|
# @rbs return: Integer
|
|
@@ -153,7 +163,7 @@ module LLDB
|
|
|
153
163
|
deref_ptr = FFIBindings.lldb_value_dereference(@ptr)
|
|
154
164
|
return nil if deref_ptr.nil? || deref_ptr.null?
|
|
155
165
|
|
|
156
|
-
Value.new(deref_ptr, parent: self)
|
|
166
|
+
Value.new(deref_ptr, parent: self, context: context)
|
|
157
167
|
end
|
|
158
168
|
|
|
159
169
|
# @rbs return: Value?
|
|
@@ -163,7 +173,7 @@ module LLDB
|
|
|
163
173
|
addr_ptr = FFIBindings.lldb_value_address_of(@ptr)
|
|
164
174
|
return nil if addr_ptr.nil? || addr_ptr.null?
|
|
165
175
|
|
|
166
|
-
Value.new(addr_ptr, parent: self)
|
|
176
|
+
Value.new(addr_ptr, parent: self, context: context)
|
|
167
177
|
end
|
|
168
178
|
|
|
169
179
|
# @rbs return: String
|
|
@@ -189,7 +199,7 @@ module LLDB
|
|
|
189
199
|
type_ptr = FFIBindings.lldb_value_get_type(@ptr)
|
|
190
200
|
return nil if type_ptr.nil? || type_ptr.null?
|
|
191
201
|
|
|
192
|
-
Type.new(type_ptr)
|
|
202
|
+
Type.new(type_ptr, context: context)
|
|
193
203
|
end
|
|
194
204
|
|
|
195
205
|
# @rbs target_type: Type
|
|
@@ -200,12 +210,12 @@ module LLDB
|
|
|
200
210
|
cast_ptr = FFIBindings.lldb_value_cast(@ptr, target_type.to_ptr)
|
|
201
211
|
return nil if cast_ptr.nil? || cast_ptr.null?
|
|
202
212
|
|
|
203
|
-
Value.new(cast_ptr, parent: self)
|
|
213
|
+
Value.new(cast_ptr, parent: self, context: context)
|
|
204
214
|
end
|
|
205
215
|
|
|
206
216
|
# @rbs return: Integer
|
|
207
217
|
def load_address
|
|
208
|
-
return
|
|
218
|
+
return INVALID_ADDRESS unless valid?
|
|
209
219
|
|
|
210
220
|
FFIBindings.lldb_value_get_load_address(@ptr)
|
|
211
221
|
end
|
|
@@ -243,7 +253,7 @@ module LLDB
|
|
|
243
253
|
child_ptr = FFIBindings.lldb_value_create_child_at_offset(@ptr, name, value_type.to_ptr, offset)
|
|
244
254
|
return nil if child_ptr.nil? || child_ptr.null?
|
|
245
255
|
|
|
246
|
-
Value.new(child_ptr, parent: self)
|
|
256
|
+
Value.new(child_ptr, parent: self, context: context)
|
|
247
257
|
end
|
|
248
258
|
|
|
249
259
|
# @rbs name: String
|
|
@@ -256,7 +266,7 @@ module LLDB
|
|
|
256
266
|
value_ptr = FFIBindings.lldb_value_create_value_from_address(@ptr, name, address, value_type.to_ptr)
|
|
257
267
|
return nil if value_ptr.nil? || value_ptr.null?
|
|
258
268
|
|
|
259
|
-
Value.new(value_ptr, parent: self)
|
|
269
|
+
Value.new(value_ptr, parent: self, context: context)
|
|
260
270
|
end
|
|
261
271
|
|
|
262
272
|
# @rbs name: String
|
|
@@ -268,7 +278,7 @@ module LLDB
|
|
|
268
278
|
value_ptr = FFIBindings.lldb_value_create_value_from_expression(@ptr, name, expression)
|
|
269
279
|
return nil if value_ptr.nil? || value_ptr.null?
|
|
270
280
|
|
|
271
|
-
Value.new(value_ptr, parent: self)
|
|
281
|
+
Value.new(value_ptr, parent: self, context: context)
|
|
272
282
|
end
|
|
273
283
|
|
|
274
284
|
# @rbs resolve: bool
|
|
@@ -285,7 +295,7 @@ module LLDB
|
|
|
285
295
|
return nil if wp_ptr.nil? || wp_ptr.null?
|
|
286
296
|
|
|
287
297
|
target = get_target_from_parent
|
|
288
|
-
Watchpoint.new(wp_ptr, target: target)
|
|
298
|
+
Watchpoint.new(wp_ptr, target: target, context: context)
|
|
289
299
|
end
|
|
290
300
|
|
|
291
301
|
# @rbs return: String?
|
|
@@ -309,7 +319,7 @@ module LLDB
|
|
|
309
319
|
value_ptr = FFIBindings.lldb_value_get_non_synthetic_value(@ptr)
|
|
310
320
|
return nil if value_ptr.nil? || value_ptr.null?
|
|
311
321
|
|
|
312
|
-
Value.new(value_ptr, parent: self)
|
|
322
|
+
Value.new(value_ptr, parent: self, context: context)
|
|
313
323
|
end
|
|
314
324
|
|
|
315
325
|
# @rbs return: FFI::Pointer
|
data/lib/lldb/value_list.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
module LLDB
|
|
6
6
|
class ValueList
|
|
7
|
+
prepend NativeLifecycle
|
|
7
8
|
include Enumerable #[Value]
|
|
8
9
|
|
|
9
10
|
# @rbs return: Frame | Value
|
|
@@ -12,10 +13,13 @@ module LLDB
|
|
|
12
13
|
# @rbs ptr: FFI::Pointer
|
|
13
14
|
# @rbs parent: Frame | Value
|
|
14
15
|
# @rbs return: void
|
|
15
|
-
def initialize(ptr, parent:)
|
|
16
|
-
@ptr = ptr # : FFI::Pointer
|
|
16
|
+
def initialize(ptr, parent:, context: parent.context)
|
|
17
17
|
@parent = parent
|
|
18
|
-
|
|
18
|
+
initialize_native_object(
|
|
19
|
+
ptr,
|
|
20
|
+
release: ->(released) { FFIBindings.lldb_value_list_destroy(released) },
|
|
21
|
+
context: context
|
|
22
|
+
)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
# @rbs ptr: FFI::Pointer
|
|
@@ -46,7 +50,7 @@ module LLDB
|
|
|
46
50
|
value_ptr = FFIBindings.lldb_value_list_get_value_at_index(@ptr, index)
|
|
47
51
|
return nil if value_ptr.nil? || value_ptr.null?
|
|
48
52
|
|
|
49
|
-
Value.new(value_ptr, parent: @parent)
|
|
53
|
+
Value.new(value_ptr, parent: @parent, context: context)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
alias [] value_at_index
|
|
@@ -59,7 +63,7 @@ module LLDB
|
|
|
59
63
|
value_ptr = FFIBindings.lldb_value_list_get_first_value_by_name(@ptr, name)
|
|
60
64
|
return nil if value_ptr.nil? || value_ptr.null?
|
|
61
65
|
|
|
62
|
-
Value.new(value_ptr, parent: @parent)
|
|
66
|
+
Value.new(value_ptr, parent: @parent, context: context)
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
# @rbs return: Array[Value]
|
data/lib/lldb/version.rb
CHANGED
data/lib/lldb/watchpoint.rb
CHANGED
|
@@ -4,16 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
module LLDB
|
|
6
6
|
class Watchpoint
|
|
7
|
+
prepend NativeLifecycle
|
|
8
|
+
|
|
7
9
|
# @rbs return: Target
|
|
8
10
|
attr_reader :target
|
|
9
11
|
|
|
10
12
|
# @rbs ptr: FFI::Pointer
|
|
11
13
|
# @rbs target: Target
|
|
12
14
|
# @rbs return: void
|
|
13
|
-
def initialize(ptr, target:)
|
|
14
|
-
@ptr = ptr # : FFI::Pointer
|
|
15
|
+
def initialize(ptr, target:, context: target.context)
|
|
15
16
|
@target = target
|
|
16
|
-
|
|
17
|
+
initialize_native_object(
|
|
18
|
+
ptr,
|
|
19
|
+
release: ->(released) { FFIBindings.lldb_watchpoint_destroy(released) },
|
|
20
|
+
context: context
|
|
21
|
+
)
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
# @rbs ptr: FFI::Pointer
|
|
@@ -29,7 +34,7 @@ module LLDB
|
|
|
29
34
|
|
|
30
35
|
# @rbs return: Integer
|
|
31
36
|
def id
|
|
32
|
-
return
|
|
37
|
+
return INVALID_BREAK_ID unless valid?
|
|
33
38
|
|
|
34
39
|
FFIBindings.lldb_watchpoint_get_id(@ptr)
|
|
35
40
|
end
|
|
@@ -99,7 +104,7 @@ module LLDB
|
|
|
99
104
|
|
|
100
105
|
# @rbs return: Integer
|
|
101
106
|
def watch_address
|
|
102
|
-
return
|
|
107
|
+
return INVALID_ADDRESS unless valid?
|
|
103
108
|
|
|
104
109
|
FFIBindings.lldb_watchpoint_get_watch_address(@ptr)
|
|
105
110
|
end
|
|
@@ -113,16 +118,28 @@ module LLDB
|
|
|
113
118
|
|
|
114
119
|
# @rbs return: bool
|
|
115
120
|
def watching_reads?
|
|
116
|
-
|
|
121
|
+
raise InvalidObjectError, 'Watchpoint is not valid' unless valid?
|
|
122
|
+
APISupport.require_feature!(:watchpoint_access_kind)
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
result = FFI::MemoryPointer.new(:int)
|
|
125
|
+
status = FFIBindings.lldb_watchpoint_is_watching_reads(@ptr, result)
|
|
126
|
+
raise UnsupportedAPIError, 'Watchpoint read access is unsupported' if status == NativeStatus::UNSUPPORTED
|
|
127
|
+
raise LLDBError, "Failed to query watchpoint read access (status=#{status})" unless status == NativeStatus::OK
|
|
128
|
+
|
|
129
|
+
result.read_int != 0
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
# @rbs return: bool
|
|
122
133
|
def watching_writes?
|
|
123
|
-
|
|
134
|
+
raise InvalidObjectError, 'Watchpoint is not valid' unless valid?
|
|
135
|
+
APISupport.require_feature!(:watchpoint_access_kind)
|
|
136
|
+
|
|
137
|
+
result = FFI::MemoryPointer.new(:int)
|
|
138
|
+
status = FFIBindings.lldb_watchpoint_is_watching_writes(@ptr, result)
|
|
139
|
+
raise UnsupportedAPIError, 'Watchpoint write access is unsupported' if status == NativeStatus::UNSUPPORTED
|
|
140
|
+
raise LLDBError, "Failed to query watchpoint write access (status=#{status})" unless status == NativeStatus::OK
|
|
124
141
|
|
|
125
|
-
|
|
142
|
+
result.read_int != 0
|
|
126
143
|
end
|
|
127
144
|
|
|
128
145
|
# @rbs return: bool
|
data/lib/lldb.rb
CHANGED
|
@@ -3,10 +3,25 @@
|
|
|
3
3
|
# rbs_inline: enabled
|
|
4
4
|
|
|
5
5
|
require_relative 'lldb/version'
|
|
6
|
+
require_relative 'lldb/error'
|
|
6
7
|
require_relative 'lldb/ffi_bindings'
|
|
7
8
|
require_relative 'lldb/types'
|
|
8
|
-
require_relative 'lldb/
|
|
9
|
+
require_relative 'lldb/native'
|
|
10
|
+
require_relative 'lldb/native_handle'
|
|
11
|
+
require_relative 'lldb/context'
|
|
12
|
+
require_relative 'lldb/native_lifecycle'
|
|
13
|
+
require_relative 'lldb/native_buffer'
|
|
9
14
|
require_relative 'lldb/api_support'
|
|
15
|
+
require_relative 'lldb/native_string_array'
|
|
16
|
+
require_relative 'lldb/file_spec'
|
|
17
|
+
require_relative 'lldb/file_spec_list'
|
|
18
|
+
require_relative 'lldb/address'
|
|
19
|
+
require_relative 'lldb/line_entry'
|
|
20
|
+
require_relative 'lldb/attach_info'
|
|
21
|
+
require_relative 'lldb/expression_options'
|
|
22
|
+
require_relative 'lldb/broadcaster'
|
|
23
|
+
require_relative 'lldb/listener'
|
|
24
|
+
require_relative 'lldb/event'
|
|
10
25
|
require_relative 'lldb/debugger'
|
|
11
26
|
require_relative 'lldb/target'
|
|
12
27
|
require_relative 'lldb/launch_info'
|
|
@@ -19,6 +34,13 @@ require_relative 'lldb/breakpoint_location'
|
|
|
19
34
|
require_relative 'lldb/value'
|
|
20
35
|
require_relative 'lldb/value_list'
|
|
21
36
|
require_relative 'lldb/type'
|
|
37
|
+
require_relative 'lldb/type_member'
|
|
38
|
+
require_relative 'lldb/symbol'
|
|
39
|
+
require_relative 'lldb/function'
|
|
40
|
+
require_relative 'lldb/compile_unit'
|
|
41
|
+
require_relative 'lldb/block'
|
|
42
|
+
require_relative 'lldb/instruction'
|
|
43
|
+
require_relative 'lldb/instruction_list'
|
|
22
44
|
require_relative 'lldb/watchpoint'
|
|
23
45
|
require_relative 'lldb/module'
|
|
24
46
|
require_relative 'lldb/symbol_context'
|
|
@@ -29,20 +51,29 @@ module LLDB
|
|
|
29
51
|
class << self
|
|
30
52
|
# @rbs return: void
|
|
31
53
|
def initialize
|
|
32
|
-
|
|
54
|
+
lifecycle_mutex.synchronize do
|
|
55
|
+
return if @initialized
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
error = Error.new
|
|
58
|
+
status = FFIBindings.lldb_initialize(error.to_ptr)
|
|
59
|
+
Native.check_status!(status, 'lldb.initialize', error)
|
|
60
|
+
@initialized = true
|
|
36
61
|
|
|
37
|
-
|
|
62
|
+
at_exit { terminate if open_debugger_count.zero? }
|
|
63
|
+
end
|
|
38
64
|
end
|
|
39
65
|
|
|
40
66
|
# @rbs return: void
|
|
41
67
|
def terminate
|
|
42
|
-
|
|
68
|
+
lifecycle_mutex.synchronize do
|
|
69
|
+
return unless @initialized
|
|
70
|
+
if live_debugger_count.positive?
|
|
71
|
+
raise LifecycleError, 'cannot terminate LLDB while debuggers are open'
|
|
72
|
+
end
|
|
43
73
|
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
FFIBindings.lldb_terminate
|
|
75
|
+
@initialized = false
|
|
76
|
+
end
|
|
46
77
|
end
|
|
47
78
|
|
|
48
79
|
# @rbs return: bool
|
|
@@ -58,9 +89,46 @@ module LLDB
|
|
|
58
89
|
end
|
|
59
90
|
|
|
60
91
|
# @rbs return: Debugger
|
|
61
|
-
def create_debugger
|
|
92
|
+
def create_debugger(source_init_files: false)
|
|
62
93
|
LLDB.initialize
|
|
63
|
-
Debugger.create
|
|
94
|
+
Debugger.create(source_init_files: source_init_files)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @rbs return: Integer
|
|
98
|
+
def open_debugger_count
|
|
99
|
+
lifecycle_mutex.synchronize { live_debugger_count }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @rbs debugger: Debugger
|
|
103
|
+
# @rbs return: void
|
|
104
|
+
def register_debugger(debugger)
|
|
105
|
+
lifecycle_mutex.synchronize do
|
|
106
|
+
debuggers = (@debuggers ||= []) # : Array[WeakRef]
|
|
107
|
+
debuggers << WeakRef.new(debugger)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# @rbs return: Integer
|
|
114
|
+
def live_debugger_count
|
|
115
|
+
live = [] # : Array[WeakRef]
|
|
116
|
+
count = (@debuggers || []).count do |reference|
|
|
117
|
+
begin
|
|
118
|
+
debugger = reference.__getobj__
|
|
119
|
+
live << reference
|
|
120
|
+
!debugger.closed?
|
|
121
|
+
rescue WeakRef::RefError
|
|
122
|
+
false
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
@debuggers = live
|
|
126
|
+
count
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs return: Mutex
|
|
130
|
+
def lifecycle_mutex
|
|
131
|
+
@lifecycle_mutex ||= Mutex.new
|
|
64
132
|
end
|
|
65
133
|
end
|
|
66
134
|
end
|
data/lldb.gemspec
CHANGED
|
@@ -25,7 +25,10 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
generated_extension_file = %r{\Aext/lldb/(?:Makefile|mkmf\.log|lldb_wrapper_config\.h|.*\.(?:o|so|dylib|dll))\z}
|
|
29
|
+
spec.files += Dir['lib/**/*.rb', 'ext/**/*'].select do |file|
|
|
30
|
+
File.file?(file) && !file.match?(generated_extension_file)
|
|
31
|
+
end
|
|
29
32
|
spec.extensions = ['ext/lldb/extconf.rb']
|
|
30
33
|
spec.require_paths = ['lib']
|
|
31
34
|
|
data/rbs_collection.yaml
CHANGED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'open3'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
require 'rbconfig'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
require 'tempfile'
|
|
10
|
+
require 'tmpdir'
|
|
11
|
+
require 'yaml'
|
|
12
|
+
|
|
13
|
+
ROOT = File.expand_path('..', __dir__)
|
|
14
|
+
|
|
15
|
+
options = {
|
|
16
|
+
header: File.join(ROOT, 'ext/lldb/lldb_wrapper.h'),
|
|
17
|
+
source: File.join(ROOT, 'ext/lldb/lldb_wrapper.cpp'),
|
|
18
|
+
ffi: File.join(ROOT, 'lib/lldb/ffi_bindings.rb'),
|
|
19
|
+
rbs: File.join(ROOT, 'sig/lldb/ffi_bindings.rbs'),
|
|
20
|
+
surface: File.join(ROOT, 'bindings/surface.yml'),
|
|
21
|
+
constants: File.join(ROOT, 'bindings/constants.yml'),
|
|
22
|
+
library: nil,
|
|
23
|
+
include: ENV['LLDB_INCLUDE'],
|
|
24
|
+
skip_library: false,
|
|
25
|
+
skip_constants: false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
OptionParser.new do |parser|
|
|
29
|
+
parser.banner = 'Usage: script/check_bindings [options]'
|
|
30
|
+
parser.on('--header PATH') { |value| options[:header] = value }
|
|
31
|
+
parser.on('--source PATH') { |value| options[:source] = value }
|
|
32
|
+
parser.on('--ffi PATH') { |value| options[:ffi] = value }
|
|
33
|
+
parser.on('--rbs PATH') { |value| options[:rbs] = value }
|
|
34
|
+
parser.on('--surface PATH') { |value| options[:surface] = value }
|
|
35
|
+
parser.on('--constants PATH') { |value| options[:constants] = value }
|
|
36
|
+
parser.on('--library PATH') { |value| options[:library] = value }
|
|
37
|
+
parser.on('--include PATH') { |value| options[:include] = value }
|
|
38
|
+
parser.on('--skip-library') { options[:skip_library] = true }
|
|
39
|
+
parser.on('--skip-constants') { options[:skip_constants] = true }
|
|
40
|
+
end.parse!
|
|
41
|
+
|
|
42
|
+
def read_required(path)
|
|
43
|
+
abort "missing binding input: #{path}" unless File.file?(path)
|
|
44
|
+
|
|
45
|
+
File.read(path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def header_functions(source)
|
|
49
|
+
source = source.gsub(%r{/\*.*?\*/|//[^\n]*}, '')
|
|
50
|
+
source.scan(/(?:^|\n)\s*(?:[A-Za-z_][A-Za-z0-9_:<>*&\s]*?)\s+(lldb_[A-Za-z0-9_]+)\s*\([^;]*\)\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?;/m).flatten.uniq.sort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ffi_functions(source)
|
|
54
|
+
source.scan(/\battach_function\s+:([a-z0-9_]+)/).flatten.uniq.sort
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def rbs_functions(source)
|
|
58
|
+
source.scan(/\bdef self\.(lldb_[a-z0-9_]+):/).flatten.uniq.sort
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def exported_functions(path)
|
|
62
|
+
command = if RbConfig::CONFIG['host_os'].match?(/darwin/)
|
|
63
|
+
['nm', '-gU', path]
|
|
64
|
+
else
|
|
65
|
+
['nm', '-D', '--defined-only', path]
|
|
66
|
+
end
|
|
67
|
+
stdout, stderr, status = Open3.capture3(*command)
|
|
68
|
+
abort "cannot inspect native library #{path}: #{stderr.strip}" unless status.success?
|
|
69
|
+
|
|
70
|
+
stdout.lines.filter_map do |line|
|
|
71
|
+
name = line.split.last
|
|
72
|
+
next unless name&.match?(/\A_?lldb_[A-Za-z0-9_]+\z/)
|
|
73
|
+
|
|
74
|
+
name.delete_prefix('_').to_sym.to_s
|
|
75
|
+
end.uniq.sort
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def report_difference(label, left_name, left, right_name, right)
|
|
79
|
+
missing = left - right
|
|
80
|
+
extra = right - left
|
|
81
|
+
return [] if missing.empty? && extra.empty?
|
|
82
|
+
|
|
83
|
+
messages = []
|
|
84
|
+
messages << "#{label}: missing from #{right_name}: #{missing.join(', ')}" unless missing.empty?
|
|
85
|
+
messages << "#{label}: missing from #{left_name}: #{extra.join(', ')}" unless extra.empty?
|
|
86
|
+
messages
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def matching_parenthesis(source, opening)
|
|
90
|
+
depth = 0
|
|
91
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
92
|
+
depth += 1 if byte == 40
|
|
93
|
+
depth -= 1 if byte == 41
|
|
94
|
+
return opening + offset if depth.zero?
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def function_body(source, name)
|
|
100
|
+
opening = nil
|
|
101
|
+
source.to_enum(:scan, /\b#{Regexp.escape(name)}\s*\(/m).each do
|
|
102
|
+
match = Regexp.last_match
|
|
103
|
+
opening_parenthesis = match.begin(0) + match[0].index('(')
|
|
104
|
+
closing_parenthesis = matching_parenthesis(source, opening_parenthesis)
|
|
105
|
+
next unless closing_parenthesis
|
|
106
|
+
|
|
107
|
+
suffix = source.byteslice((closing_parenthesis + 1)..).to_s
|
|
108
|
+
opening_offset = suffix.index(/\A\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?\{/m)
|
|
109
|
+
next unless opening_offset
|
|
110
|
+
|
|
111
|
+
opening = closing_parenthesis + 1 + opening_offset + suffix[opening_offset..].index('{')
|
|
112
|
+
break
|
|
113
|
+
end
|
|
114
|
+
return nil unless opening
|
|
115
|
+
|
|
116
|
+
depth = 0
|
|
117
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
118
|
+
depth += 1 if byte == 123
|
|
119
|
+
depth -= 1 if byte == 125
|
|
120
|
+
return source.byteslice(opening, offset + 1) if depth.zero?
|
|
121
|
+
end
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def check_surface(path, functions, source)
|
|
126
|
+
document = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
127
|
+
entries = document.fetch('entries')
|
|
128
|
+
errors = []
|
|
129
|
+
|
|
130
|
+
errors << "surface ledger has missing entries: #{(functions - entries.keys).sort.join(', ')}" unless
|
|
131
|
+
(functions - entries.keys).empty?
|
|
132
|
+
errors << "surface ledger has stale entries: #{(entries.keys - functions).sort.join(', ')}" unless
|
|
133
|
+
(entries.keys - functions).empty?
|
|
134
|
+
|
|
135
|
+
allowed_classifications = %w[public internal metadata optional_stub deprecated]
|
|
136
|
+
entries.each do |name, entry|
|
|
137
|
+
next unless functions.include?(name)
|
|
138
|
+
|
|
139
|
+
classification = entry.fetch('classification', '')
|
|
140
|
+
unless allowed_classifications.include?(classification)
|
|
141
|
+
errors << "surface ledger entry #{name} has invalid classification #{classification.inspect}"
|
|
142
|
+
end
|
|
143
|
+
if entry.fetch('reason', '').to_s.strip.empty?
|
|
144
|
+
errors << "surface ledger entry #{name} has no reason"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
guard = entry.fetch('exception_guard', {})
|
|
148
|
+
kind = guard.fetch('kind', '')
|
|
149
|
+
reason = guard.fetch('reason', '').to_s.strip
|
|
150
|
+
if reason.empty?
|
|
151
|
+
errors << "surface ledger entry #{name} has no exception guard reason"
|
|
152
|
+
elsif kind == 'error_boundary'
|
|
153
|
+
body = function_body(source, name)
|
|
154
|
+
unless body&.include?('try') && body.include?('catch')
|
|
155
|
+
errors << "#{name} is marked error_boundary but has no try/catch body"
|
|
156
|
+
end
|
|
157
|
+
unless source.match?(/\b#{Regexp.escape(name)}\s*\([^;]*\)\s*LLDB_WRAPPER_NOEXCEPT\s*\{/m)
|
|
158
|
+
errors << "#{name} is marked error_boundary but is not declared noexcept"
|
|
159
|
+
end
|
|
160
|
+
elsif kind != 'noexcept'
|
|
161
|
+
errors << "surface ledger entry #{name} has invalid exception guard #{kind.inspect}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
methods = document.fetch('ruby_methods', [])
|
|
166
|
+
methods.each do |mapping|
|
|
167
|
+
file = File.join(ROOT, mapping.fetch('file'))
|
|
168
|
+
method = mapping.fetch('method')
|
|
169
|
+
unless File.file?(file) && File.read(file).match?(/^\s*def #{Regexp.escape(method)}\b/)
|
|
170
|
+
errors << "#{mapping.fetch('function')} maps to missing Ruby method #{mapping.fetch('method')} in #{file}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
errors
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def find_include_dir(explicit)
|
|
177
|
+
return explicit if explicit && File.file?(File.join(explicit, 'lldb/API/LLDB.h'))
|
|
178
|
+
|
|
179
|
+
discovery = File.join(ROOT, 'ext/lldb/discovery.rb')
|
|
180
|
+
require discovery
|
|
181
|
+
LLDB::BuildDiscovery.candidates.find do |candidate|
|
|
182
|
+
File.file?(File.join(candidate.include_dir.to_s, 'lldb/API/LLDB.h'))
|
|
183
|
+
end&.include_dir
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def check_constants(path, include_dir)
|
|
187
|
+
document = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
188
|
+
constants = document.fetch('constants')
|
|
189
|
+
return [] if constants.empty?
|
|
190
|
+
|
|
191
|
+
unless include_dir
|
|
192
|
+
return ['cannot check constants: LLDB include directory was not found']
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
source = <<~CPP
|
|
196
|
+
#include <lldb/API/LLDB.h>
|
|
197
|
+
#include <iostream>
|
|
198
|
+
int main() {
|
|
199
|
+
#{constants.map { |entry| " std::cout << static_cast<unsigned long long>(#{entry.fetch('native')}) << '\\n';" }.join("\n")}
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
CPP
|
|
203
|
+
|
|
204
|
+
Dir.mktmpdir('lldb-ruby-constants') do |directory|
|
|
205
|
+
source_path = File.join(directory, 'probe.cpp')
|
|
206
|
+
binary_path = File.join(directory, 'probe')
|
|
207
|
+
File.write(source_path, source)
|
|
208
|
+
compiler = Shellwords.split(ENV.fetch('CXX', RbConfig::CONFIG['CXX'] || 'c++'))
|
|
209
|
+
_stdout, stderr, status = Open3.capture3(
|
|
210
|
+
*compiler, '-std=c++17', '-I', include_dir, source_path, '-o', binary_path
|
|
211
|
+
)
|
|
212
|
+
return ["constant probe compilation failed: #{stderr.strip}"] unless status.success?
|
|
213
|
+
|
|
214
|
+
native_output, native_error, native_status = Open3.capture3(binary_path)
|
|
215
|
+
return ["constant probe failed: #{native_error.strip}"] unless native_status.success?
|
|
216
|
+
|
|
217
|
+
ruby_code = constants.map { |entry| "puts LLDB::#{entry.fetch('ruby')}" }.join('; ')
|
|
218
|
+
ruby_output, ruby_error, ruby_status = Open3.capture3(
|
|
219
|
+
RbConfig.ruby, '-I', File.join(ROOT, 'lib'), '-r', 'lldb/types', '-e', ruby_code
|
|
220
|
+
)
|
|
221
|
+
return ["Ruby constant probe failed: #{ruby_error.strip}"] unless ruby_status.success?
|
|
222
|
+
|
|
223
|
+
native_values = native_output.lines.map(&:strip)
|
|
224
|
+
ruby_values = ruby_output.lines.map(&:strip)
|
|
225
|
+
errors = []
|
|
226
|
+
constants.each_with_index do |entry, index|
|
|
227
|
+
next if native_values[index] == ruby_values[index]
|
|
228
|
+
|
|
229
|
+
errors << "constant #{entry.fetch('ruby')} differs: Ruby=#{ruby_values[index].inspect}, " \
|
|
230
|
+
"LLDB=#{native_values[index].inspect}"
|
|
231
|
+
end
|
|
232
|
+
errors
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
header = header_functions(read_required(options[:header]))
|
|
237
|
+
ffi = ffi_functions(read_required(options[:ffi]))
|
|
238
|
+
rbs = rbs_functions(read_required(options[:rbs]))
|
|
239
|
+
source = read_required(options[:source])
|
|
240
|
+
errors = []
|
|
241
|
+
errors.concat(report_difference('header/FFI', 'header', header, 'FFI', ffi))
|
|
242
|
+
errors.concat(report_difference('FFI/RBS', 'FFI', ffi, 'RBS', rbs))
|
|
243
|
+
|
|
244
|
+
unless options[:skip_library]
|
|
245
|
+
library = options[:library] || Dir[File.join(ROOT, 'lib/lldb/liblldb_wrapper.{so,dylib,dll}')].first
|
|
246
|
+
errors << 'native library was not found; pass --library or --skip-library' unless library
|
|
247
|
+
errors.concat(report_difference('header/native exports', 'header', header, 'native exports', exported_functions(library))) if library
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
errors.concat(check_surface(options[:surface], header, source))
|
|
251
|
+
errors.concat(check_constants(options[:constants], find_include_dir(options[:include]))) unless options[:skip_constants]
|
|
252
|
+
|
|
253
|
+
if errors.empty?
|
|
254
|
+
puts "binding parity OK (#{header.length} functions)"
|
|
255
|
+
exit 0
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
warn errors.map { |error| "error: #{error}" }.join("\n")
|
|
259
|
+
exit 1
|