15
15
from collections import namedtuple
16
16
import json
17
17
import os
18
+ import re
19
+ import subprocess
18
20
import sys
19
21
from tools import shared
20
22
from tools import webassembly
21
- from tools .shared import check_call
22
23
23
24
LLVM_SYMBOLIZER = os .path .expanduser (
24
25
shared .build_llvm_tool_path (shared .exe_suffix ('llvm-symbolizer' )))
@@ -28,6 +29,20 @@ class Error(BaseException):
28
29
pass
29
30
30
31
32
+ # Class to treat location info in a uniform way across information sources.
33
+ class LocationInfo (object ):
34
+ def __init__ (self , source = None , line = 0 , column = 0 , func = None ):
35
+ self .source = source
36
+ self .line = line
37
+ self .column = column
38
+ self .func = func
39
+
40
+ def print (self ):
41
+ source = self .source if self .source else '??'
42
+ func = self .func if self .func else '??'
43
+ print (f'{ func } \n { source } :{ self .line } :{ self .column } ' )
44
+
45
+
31
46
def get_codesec_offset (module ):
32
47
sec = module .get_section (webassembly .SecType .CODE )
33
48
if not sec :
@@ -46,7 +61,24 @@ def symbolize_address_dwarf(module, address):
46
61
vma_adjust = get_codesec_offset (module )
47
62
cmd = [LLVM_SYMBOLIZER , '-e' , module .filename , f'--adjust-vma={ vma_adjust } ' ,
48
63
str (address )]
49
- check_call (cmd )
64
+ out = shared .run_process (cmd , stdout = subprocess .PIPE ).stdout .strip ()
65
+ out_lines = out .splitlines ()
66
+ # Source location regex, e.g., /abc/def.c:3:5
67
+ SOURCE_LOC_RE = re .compile (r'(.+):(\d+):(\d+)$' )
68
+ # llvm-dwarfdump prints two lines per location. The first line contains a
69
+ # function name, and the second contains a source location like
70
+ # '/abc/def.c:3:5'. If the function or source info is not available, it will
71
+ # be printed as '??', in which case we store None. If the line and column info
72
+ # is not available, they will be printed as 0, which we store as is.
73
+ for i in range (0 , len (out_lines ), 2 ):
74
+ func , loc_str = out_lines [i ], out_lines [i + 1 ]
75
+ m = SOURCE_LOC_RE .match (loc_str )
76
+ source , line , column = m .group (1 ), m .group (2 ), m .group (3 )
77
+ if func == '??' :
78
+ func = None
79
+ if source == '??' :
80
+ source = None
81
+ LocationInfo (source , line , column , func ).print ()
50
82
51
83
52
84
def get_sourceMappingURL_section (module ):
@@ -58,14 +90,12 @@ def get_sourceMappingURL_section(module):
58
90
59
91
class WasmSourceMap (object ):
60
92
# This implementation is derived from emscripten's sourcemap-support.js
61
- Location = namedtuple ('Location' ,
62
- ['source' , 'line' , 'column' , 'name' ])
93
+ Location = namedtuple ('Location' , ['source' , 'line' , 'column' ])
63
94
64
95
def __init__ (self ):
65
96
self .version = None
66
97
self .sources = []
67
- self .names = []
68
- self .mapping = {}
98
+ self .mappings = {}
69
99
self .offsets = []
70
100
71
101
def parse (self , filename ):
@@ -76,7 +106,6 @@ def parse(self, filename):
76
106
77
107
self .version = source_map_json ['version' ]
78
108
self .sources = source_map_json ['sources' ]
79
- self .names = source_map_json ['names' ]
80
109
81
110
vlq_map = {}
82
111
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
@@ -106,7 +135,6 @@ def decodeVLQ(string):
106
135
src = 0
107
136
line = 1
108
137
col = 1
109
- name = 0
110
138
for segment in source_map_json ['mappings' ].split (',' ):
111
139
data = decodeVLQ (segment )
112
140
info = []
@@ -121,11 +149,9 @@ def decodeVLQ(string):
121
149
if len (data ) >= 4 :
122
150
col += data [3 ]
123
151
info .append (col )
124
- if len (data ) >= 5 :
125
- name += data [4 ]
126
- info .append (name )
152
+ # TODO: see if we need the name, which is the next field (data[4])
127
153
128
- self .mapping [offset ] = WasmSourceMap .Location (* info )
154
+ self .mappings [offset ] = WasmSourceMap .Location (* info )
129
155
self .offsets .append (offset )
130
156
self .offsets .sort ()
131
157
@@ -144,18 +170,13 @@ def find_offset(self, offset):
144
170
145
171
def lookup (self , offset ):
146
172
nearest = self .find_offset (offset )
147
- assert nearest in self .mapping , 'Sourcemap has an offset with no mapping'
148
- info = self .mapping [nearest ]
149
-
150
- # TODO: it's kind of icky to use Location for both the internal indexed
151
- # location and external string version. Once we have more uniform output
152
- # format and API for the various backends (e.g SM vs DWARF vs others), this
153
- # could be improved.
154
- return WasmSourceMap .Location (
173
+ assert nearest in self .mappings , 'Sourcemap has an offset with no mapping'
174
+ info = self .mappings [nearest ]
175
+ return LocationInfo (
155
176
self .sources [info .source ] if info .source is not None else None ,
156
177
info .line ,
157
- info .column ,
158
- self . names [ info . name ] if info . name is not None else None )
178
+ info .column
179
+ )
159
180
160
181
161
182
def symbolize_address_sourcemap (module , address , force_file ):
@@ -175,11 +196,11 @@ def symbolize_address_sourcemap(module, address, force_file):
175
196
sm .parse (URL )
176
197
if shared .DEBUG :
177
198
csoff = get_codesec_offset (module )
178
- print (sm .mapping )
199
+ print (sm .mappings )
179
200
# Print with section offsets to easily compare against dwarf
180
- for k , v in sm .mapping .items ():
201
+ for k , v in sm .mappings .items ():
181
202
print (f'{ k - csoff :x} : { v } ' )
182
- print ( sm .lookup (address ))
203
+ sm .lookup (address ). print ( )
183
204
184
205
185
206
def main (args ):
@@ -228,8 +249,6 @@ def get_args():
228
249
229
250
230
251
if __name__ == '__main__' :
231
- print ('Warning: the command-line and output format of this tool are not '
232
- 'finalized yet' , file = sys .stderr )
233
252
try :
234
253
rv = main (get_args ())
235
254
except (Error , webassembly .InvalidWasmError , OSError ) as e :
0 commit comments