Skip to content

Commit ac61667

Browse files
committed
Merge remote-tracking branch 'origin/GP-5217_James_source_map_vscode_script--SQUASHED'
2 parents 5b4b055 + 22f51ed commit ac61667

File tree

2 files changed

+337
-0
lines changed

2 files changed

+337
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* ###
2+
* IP: GHIDRA
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
// This script reads the source map information for the current address and uses it to open
17+
// a source file in eclipse at the appropriate line. If there are multiple source map entries
18+
// at the current address, the script displays a table to allow the user to select which ones
19+
// to send to eclipse. The source file paths can be adjusted via
20+
//
21+
// Window -> Source Files and Transforms
22+
//
23+
// from the Code Browser. The path to the eclipse installation directory can be set via
24+
//
25+
// Edit -> Tool Options -> Eclipse Integration
26+
//
27+
// from the Ghidra Project Manager.
28+
//@category SourceMapping
29+
import java.io.File;
30+
import java.io.FileNotFoundException;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
34+
import ghidra.app.services.EclipseIntegrationService;
35+
import ghidra.util.task.MonitoredRunnable;
36+
import ghidra.util.task.TaskBuilder;
37+
38+
public class OpenSourceFileAtLineInEclipseScript extends OpenSourceFileAtLineInVSCodeScript {
39+
40+
private EclipseIntegrationService eclipseService;
41+
42+
@Override
43+
protected boolean verifyAndSetIdeExe() {
44+
eclipseService = state.getTool().getService(EclipseIntegrationService.class);
45+
if (eclipseService == null) {
46+
popup("Eclipse service not configured for tool");
47+
return false;
48+
}
49+
try {
50+
ideExecutableFile = eclipseService.getEclipseExecutableFile();
51+
}
52+
catch (FileNotFoundException e) {
53+
printerr(e.getMessage());
54+
return false;
55+
}
56+
return true;
57+
}
58+
59+
@Override
60+
protected void openInIde(String transformedPath, int lineNumber) {
61+
// transformedPath is a file uri path so it uses forward slashes
62+
// File constructor on windows can accept such paths
63+
File localSourceFile = new File(transformedPath);
64+
if (!localSourceFile.exists()) {
65+
popup(transformedPath + " does not exist");
66+
return;
67+
}
68+
69+
MonitoredRunnable r = m -> {
70+
try {
71+
List<String> args = new ArrayList<>();
72+
args.add(ideExecutableFile.getAbsolutePath());
73+
args.add(localSourceFile.getAbsolutePath() + ":" + lineNumber);
74+
new ProcessBuilder(args).redirectErrorStream(true).start();
75+
}
76+
catch (Exception e) {
77+
eclipseService.handleEclipseError(
78+
"Unexpected exception occurred while launching Eclipse.", false,
79+
null);
80+
return;
81+
}
82+
};
83+
84+
new TaskBuilder("Opening File in Eclipse", r)
85+
.setHasProgress(false)
86+
.setCanCancel(true)
87+
.launchModal();
88+
return;
89+
90+
}
91+
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/* ###
2+
* IP: GHIDRA
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
// This script reads the source map information for the current address and uses it to open
17+
// a source file in vs code at the appropriate line. If there are multiple source map entries
18+
// at the current address, the script displays a table to allow the user to select which ones
19+
// to send to vs code. The source file paths can be adjusted via
20+
//
21+
// Window -> Source Files and Transforms
22+
//
23+
// from the Code Browser. The path to the vs code executable can be set via
24+
//
25+
// Edit -> Tool Options -> Visual Studio Code Integration
26+
//
27+
// from the Ghidra Project Manager.
28+
//@category SourceMapping
29+
30+
import java.io.File;
31+
import java.io.FileNotFoundException;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
import ghidra.app.script.GhidraScript;
36+
import ghidra.app.services.VSCodeIntegrationService;
37+
import ghidra.app.tablechooser.*;
38+
import ghidra.program.database.sourcemap.UserDataPathTransformer;
39+
import ghidra.program.model.address.Address;
40+
import ghidra.program.model.sourcemap.SourceMapEntry;
41+
import ghidra.program.model.sourcemap.SourcePathTransformer;
42+
import ghidra.util.task.MonitoredRunnable;
43+
import ghidra.util.task.TaskBuilder;
44+
45+
public class OpenSourceFileAtLineInVSCodeScript extends GhidraScript {
46+
47+
private VSCodeIntegrationService vscodeService;
48+
protected SourcePathTransformer pathTransformer;
49+
protected File ideExecutableFile;
50+
51+
@Override
52+
protected void run() throws Exception {
53+
if (isRunningHeadless()) {
54+
popup("This script cannot be run headlessly.");
55+
return;
56+
}
57+
if (currentProgram == null) {
58+
popup("This script requires an open program.");
59+
return;
60+
}
61+
62+
List<SourceMapEntry> entries =
63+
currentProgram.getSourceFileManager().getSourceMapEntries(currentAddress);
64+
if (entries.isEmpty()) {
65+
popup("No source map entries found for " + currentAddress);
66+
return;
67+
}
68+
69+
if (!verifyAndSetIdeExe()) {
70+
popup("Error acquiring IDE executable");
71+
return;
72+
}
73+
74+
pathTransformer = UserDataPathTransformer.getPathTransformer(currentProgram);
75+
76+
// if there is only one source map entry, send it to IDE
77+
if (entries.size() == 1) {
78+
SourceMapEntry entry = entries.get(0);
79+
openInIde(pathTransformer.getTransformedPath(entry.getSourceFile(), true),
80+
entry.getLineNumber());
81+
}
82+
// if there are multiple entries, pop up a table and let the user pick which ones
83+
// to send to IDE
84+
else {
85+
TableChooserDialog tableDialog =
86+
createTableChooserDialog("SourceMapEntries at " + currentAddress,
87+
new OpenInIdeExecutor());
88+
configureTableColumns(tableDialog);
89+
for (SourceMapEntry entry : entries) {
90+
tableDialog.add(new LocalPathRowObject(entry));
91+
}
92+
tableDialog.show();
93+
}
94+
}
95+
96+
/**
97+
* Sets the field {@code ideExecutableField}
98+
* @return false if the IDE executable field could not be acquired
99+
*/
100+
protected boolean verifyAndSetIdeExe() {
101+
vscodeService = state.getTool().getService(VSCodeIntegrationService.class);
102+
if (vscodeService == null) {
103+
popup("VSCode service not configured for tool");
104+
return false;
105+
}
106+
try {
107+
ideExecutableFile = vscodeService.getVSCodeExecutableFile();
108+
}
109+
catch (FileNotFoundException e) {
110+
printerr(e.getMessage());
111+
return false;
112+
}
113+
return true;
114+
}
115+
116+
/**
117+
* Opens the source file at {@code transformedPath} at the given line number
118+
* @param transformedPath source file path
119+
* @param lineNumber line number
120+
*/
121+
protected void openInIde(String transformedPath, int lineNumber) {
122+
// transformedPath is a file uri path so it uses forward slashes
123+
// File constructor on windows can accept such paths
124+
File localSourceFile = new File(transformedPath);
125+
if (!localSourceFile.exists()) {
126+
popup(transformedPath + " does not exist");
127+
return;
128+
}
129+
130+
MonitoredRunnable r = m -> {
131+
try {
132+
List<String> args = new ArrayList<>();
133+
args.add(ideExecutableFile.getAbsolutePath());
134+
args.add("--goto");
135+
args.add(localSourceFile.getAbsolutePath() + ":" + lineNumber);
136+
new ProcessBuilder(args).redirectErrorStream(true).start();
137+
}
138+
catch (Exception e) {
139+
vscodeService.handleVSCodeError(
140+
"Unexpected exception occurred while launching Visual Studio Code.", false,
141+
null);
142+
return;
143+
}
144+
};
145+
146+
new TaskBuilder("Opening File in VSCode", r)
147+
.setHasProgress(false)
148+
.setCanCancel(true)
149+
.launchModal();
150+
return;
151+
}
152+
153+
////////////////table stuff //////////////////
154+
155+
private void configureTableColumns(TableChooserDialog tableDialog) {
156+
StringColumnDisplay fileNameColumn = new StringColumnDisplay() {
157+
@Override
158+
public String getColumnName() {
159+
return "Filename";
160+
}
161+
162+
@Override
163+
public String getColumnValue(AddressableRowObject rowObject) {
164+
return ((LocalPathRowObject) rowObject).getFileName();
165+
}
166+
};
167+
168+
ColumnDisplay<Integer> lineNumberColumn = new AbstractComparableColumnDisplay<>() {
169+
@Override
170+
public Integer getColumnValue(AddressableRowObject rowObject) {
171+
return ((LocalPathRowObject) rowObject).getLineNumber();
172+
}
173+
174+
@Override
175+
public String getColumnName() {
176+
return "Line Number";
177+
}
178+
};
179+
180+
StringColumnDisplay localPathColumn = new StringColumnDisplay() {
181+
182+
@Override
183+
public String getColumnValue(AddressableRowObject rowObject) {
184+
return ((LocalPathRowObject) rowObject).getLocalPath();
185+
}
186+
187+
@Override
188+
public String getColumnName() {
189+
return "Local Path";
190+
}
191+
192+
};
193+
tableDialog.addCustomColumn(fileNameColumn);
194+
tableDialog.addCustomColumn(lineNumberColumn);
195+
tableDialog.addCustomColumn(localPathColumn);
196+
}
197+
198+
class LocalPathRowObject implements AddressableRowObject {
199+
200+
private Address baseAddress;
201+
private String fileName;
202+
private String localPath;
203+
private int lineNumber;
204+
205+
LocalPathRowObject(SourceMapEntry entry) {
206+
this.baseAddress = entry.getBaseAddress();
207+
this.fileName = entry.getSourceFile().getFilename();
208+
this.lineNumber = entry.getLineNumber();
209+
this.localPath = pathTransformer.getTransformedPath(entry.getSourceFile(), true);
210+
}
211+
212+
@Override
213+
public Address getAddress() {
214+
return baseAddress;
215+
}
216+
217+
String getFileName() {
218+
return fileName;
219+
}
220+
221+
String getLocalPath() {
222+
return localPath;
223+
}
224+
225+
int getLineNumber() {
226+
return lineNumber;
227+
}
228+
}
229+
230+
class OpenInIdeExecutor implements TableChooserExecutor {
231+
232+
@Override
233+
public String getButtonName() {
234+
return "Open Source File";
235+
}
236+
237+
@Override
238+
public boolean execute(AddressableRowObject rowObject) {
239+
LocalPathRowObject row = (LocalPathRowObject) rowObject;
240+
openInIde(row.getLocalPath(), row.getLineNumber());
241+
return false;
242+
}
243+
}
244+
245+
}

0 commit comments

Comments
 (0)