Skip to content

Commit 9105b68

Browse files
authored
[move] Added source file to debug info and used it to finesse trace debugger experience (MystenLabs#21495)
## Description This PR adds source file information to debug info for the file that this info was generated for/from. Previously, dependency source files used by the trace debugger are those in the `build` directory, as they are easy to find and readily available. This however had two unfortunate consequences for the trace debugger user: - if during debugging, the user enters a function in a dependent source file, they will not have access to Move IDE's code inspection capabilities for this file (the reason for it is that source files in the `build` directory are not "buildable" by the compiler due to lack of the manifest file so the symbols required for on-hover, go-to-def etc. cannot be produced) - if the user uses go-to-def to go to a dependent source file to set a breakpoint there, the breakpoint would not trigger (the reason for it is that the dependent file reached this way is not the one in the `build` directory that is known to the trace debugger but rather the actual original dependent source file) Having the original dependent file path in debug info allows us to remedy these two shortcomings. Of course it is possible that this original dependent file path no longer exists, in which case trace debugger will default to using a file from the `build` directory. ## Test plan A new test for debug info v2 was added. All tests must pass. I also tested manually that both deficiencies were remedies when using trace debugger.
1 parent 8e938bb commit 9105b68

File tree

20 files changed

+1419
-50
lines changed

20 files changed

+1419
-50
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

external-crates/move/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

external-crates/move/crates/move-analyzer/trace-adapter/src/debug_info_utils.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) The Move Contributors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import * as crypto from 'crypto';
45
import * as fs from 'fs';
56
import * as path from 'path';
67
import { ModuleInfo } from './utils';
@@ -39,7 +40,8 @@ interface JSONSrcFunctionMapEntry {
3940
}
4041

4142
interface JSONSrcRootObject {
42-
version?: number;
43+
version?: number; // introduced in debug info v1
44+
from_file_path?: string; // introduced in debug info v2
4345
definition_location: JSONSrcDefinitionLocation;
4446
module_name: string[];
4547
struct_map: Record<string, JSONSrcStructSourceMapEntry>;
@@ -207,15 +209,10 @@ function readDebugInfo(
207209
debugInfoLinesMap: Map<string, Set<number>>,
208210
failOnNoSourceFile: boolean,
209211
): IDebugInfo | undefined {
210-
const sourceMapJSON: JSONSrcRootObject = JSON.parse(fs.readFileSync(debugInfoPath, 'utf8'));
212+
const debugInfoJSON: JSONSrcRootObject = JSON.parse(fs.readFileSync(debugInfoPath, 'utf8'));
211213

212-
const fileHash = Buffer.from(sourceMapJSON.definition_location.file_hash).toString('base64');
213-
const modInfo: ModuleInfo = {
214-
addr: sourceMapJSON.module_name[0],
215-
name: sourceMapJSON.module_name[1]
216-
};
217-
const functions = new Map<string, IDebugInfoFunction>();
218-
const fileInfo = filesMap.get(fileHash);
214+
let fileHash = Buffer.from(debugInfoJSON.definition_location.file_hash).toString('base64');
215+
let fileInfo = filesMap.get(fileHash);
219216
if (!fileInfo) {
220217
if (failOnNoSourceFile) {
221218
throw new Error('Could not find file with hash: '
@@ -226,10 +223,26 @@ function readDebugInfo(
226223
return undefined;
227224
}
228225
}
226+
227+
/// If the actual file for which debug information was generated
228+
/// still exists, use it as it will likely be "buildable" (after all
229+
/// debug info was genrated at build time) and thus will work better
230+
/// in the IDE setting (e.g., with IDE's code inspection features).
231+
if (debugInfoJSON.from_file_path !== undefined &&
232+
fs.existsSync(debugInfoJSON.from_file_path)) {
233+
[fileHash, fileInfo] = createFileInfo(debugInfoJSON.from_file_path);
234+
filesMap.set(fileHash, fileInfo);
235+
}
236+
237+
const modInfo: ModuleInfo = {
238+
addr: debugInfoJSON.module_name[0],
239+
name: debugInfoJSON.module_name[1]
240+
};
241+
const functions = new Map<string, IDebugInfoFunction>();
229242
const debugInfoLines = debugInfoLinesMap.get(fileHash) ?? new Set<number>;
230-
prePopulateDebugInfoLines(sourceMapJSON, fileInfo, debugInfoLines);
243+
prePopulateDebugInfoLines(debugInfoJSON, fileInfo, debugInfoLines);
231244
debugInfoLinesMap.set(fileHash, debugInfoLines);
232-
const functionMap = sourceMapJSON.function_map;
245+
const functionMap = debugInfoJSON.function_map;
233246
for (const funEntry of Object.values(functionMap)) {
234247
let nameStart = funEntry.definition_location.start;
235248
let nameEnd = funEntry.definition_location.end;
@@ -298,6 +311,33 @@ function readDebugInfo(
298311
return { filePath: fileInfo.path, fileHash, modInfo, functions, optimizedLines: [] };
299312
}
300313

314+
315+
/**
316+
* Creates IFileInfo for a file on a given path and returns it along with
317+
* the file hash.
318+
*
319+
* @param filePath path to the file.
320+
* @returns a tuple with the file hash and the file info.
321+
*/
322+
export function createFileInfo(filePath: string): [string, IFileInfo] {
323+
const content = fs.readFileSync(filePath, 'utf8');
324+
const numFileHash = computeFileHash(content);
325+
const lines = content.split('\n');
326+
const fileInfo = { path: filePath, content, lines };
327+
const fileHash = Buffer.from(numFileHash).toString('base64');
328+
return [fileHash, fileInfo];
329+
}
330+
331+
/**
332+
* Computes the SHA-256 hash of a file's contents.
333+
*
334+
* @param fileContents contents of the file.
335+
*/
336+
function computeFileHash(fileContents: string): Uint8Array {
337+
const hash = crypto.createHash('sha256').update(fileContents).digest();
338+
return new Uint8Array(hash);
339+
}
340+
301341
/**
302342
* Pre-populates the set of source file lines that are present in the debug info
303343
* with lines corresponding to the definitions of module, structs, enums, and functions

external-crates/move/crates/move-analyzer/trace-adapter/src/runtime.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { EventEmitter } from 'events';
5-
import * as crypto from 'crypto';
65
import * as fs from 'fs';
76
import * as path from 'path';
87
import toml from 'toml';
98
import {
9+
createFileInfo,
1010
IFileInfo,
1111
ILocalInfo,
1212
IDebugInfo,
@@ -130,23 +130,23 @@ interface IRuntimeStackFrame {
130130
/**
131131
* Path to the disassembled bytecode file containing currently executing instruction.
132132
*/
133-
bcodeFilePath: undefined | string;
133+
bcodeFilePath?: string;
134134
/**
135135
* File hash of the source file containing currently executing instruction.
136136
*/
137137
srcFileHash: string;
138138
/**
139139
* File hash of the disassembled bytecode file containing currently executing instruction.
140140
*/
141-
bcodeFileHash: undefined | string;
141+
bcodeFileHash?: string;
142142
/**
143143
* Current line in the source file corresponding to currently viewed instruction.
144144
*/
145145
srcLine: number; // 1-based
146146
/**
147147
* Current line in the disassembled bytecode file corresponding to currently viewed instruction.
148148
*/
149-
bcodeLine: undefined | number; // 1-based
149+
bcodeLine?: number; // 1-based
150150
/**
151151
* Local variable types by variable frame index.
152152
*/
@@ -165,20 +165,20 @@ interface IRuntimeStackFrame {
165165
* Line in the source file of the last call instruction that was processed in this frame.
166166
* It's needed to make sure that step/next into/over call works correctly.
167167
*/
168-
lastCallInstructionSrcLine: number | undefined;
168+
lastCallInstructionSrcLine?: number;
169169
/**
170170
* Line in the disassembled bytecode file of the last call instruction that was processed in this frame.
171171
* It's needed to make sure that step/next into/over call works correctly.
172172
*/
173-
lastCallInstructionBcodeLine: number | undefined;
173+
lastCallInstructionBcodeLine?: number;
174174
/**
175175
* Lines that are not present in the debug info.
176176
*/
177177
optimizedSrcLines: number[];
178178
/**
179179
* Lines that are not present in the bytecode map.
180180
*/
181-
optimizedBcodeLines: undefined | number[];
181+
optimizedBcodeLines?: number[];
182182
/**
183183
* Disassembly mode has been triggered (we have both
184184
* source and disassembly mode available for this frame).
@@ -378,8 +378,8 @@ export class Runtime extends EventEmitter {
378378

379379
// create a mapping from source file hash to its corresponding debug info
380380
const srcDebugInfosHashMap = new Map<string, IDebugInfo>;
381-
for (const [_, sourceMap] of srcDebugInfo) {
382-
srcDebugInfosHashMap.set(sourceMap.fileHash, sourceMap);
381+
for (const [_, info] of srcDebugInfo) {
382+
srcDebugInfosHashMap.set(info.fileHash, info);
383383
}
384384

385385
// if we are missing source debug infos (and thus source files), but have bytecode debug infos
@@ -1268,11 +1268,7 @@ function hashToFileMap(
12681268
if (stats.isDirectory()) {
12691269
processDirectory(filePath);
12701270
} else if (path.extname(f) === extension) {
1271-
const content = fs.readFileSync(filePath, 'utf8');
1272-
const numFileHash = computeFileHash(content);
1273-
const lines = content.split('\n');
1274-
const fileInfo = { path: filePath, content, lines };
1275-
const fileHash = Buffer.from(numFileHash).toString('base64');
1271+
const [fileHash, fileInfo] = createFileInfo(filePath);
12761272
filesMap.set(fileHash, fileInfo);
12771273
}
12781274
}
@@ -1391,13 +1387,3 @@ function getPkgNameFromManifest(pkgRoot: string): string | undefined {
13911387
const packageName = parsedManifest.package.name;
13921388
return packageName;
13931389
}
1394-
1395-
/**
1396-
* Computes the SHA-256 hash of a file's contents.
1397-
*
1398-
* @param fileContents contents of the file.
1399-
*/
1400-
function computeFileHash(fileContents: string): Uint8Array {
1401-
const hash = crypto.createHash('sha256').update(fileContents).digest();
1402-
return new Uint8Array(hash);
1403-
}

external-crates/move/crates/move-analyzer/trace-adapter/src/trace_utils.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,20 +244,20 @@ export type TraceEvent =
244244
id: number,
245245
name: string,
246246
srcFileHash: string
247-
bcodeFileHash: undefined | string,
247+
bcodeFileHash?: string,
248248
isNative: boolean,
249249
localsTypes: string[],
250250
localsNames: ILocalInfo[],
251251
paramValues: RuntimeValueType[]
252252
optimizedSrcLines: number[]
253-
optimizedBcodeLines: undefined | number[]
253+
optimizedBcodeLines?: number[]
254254
}
255255
| { type: TraceEventKind.CloseFrame, id: number }
256256
| {
257257
type: TraceEventKind.Instruction,
258258
pc: number,
259259
srcLoc: ILoc,
260-
bcodeLoc: undefined | ILoc,
260+
bcodeLoc?: ILoc,
261261
kind: TraceInstructionKind
262262
}
263263
| { type: TraceEventKind.Effect, effect: EventEffect };
@@ -320,23 +320,23 @@ interface ITraceGenFrameInfo {
320320
/**
321321
* Path to a disassembled bytecode file containing function represented by the frame.
322322
*/
323-
bcodeFilePath: undefined | string;
323+
bcodeFilePath?: string;
324324
/**
325325
* Hash of a source file containing function represented by the frame.
326326
*/
327327
srcFileHash: string;
328328
/**
329329
* Hash of a disassembled bytecode file containing function represented by the frame.
330330
*/
331-
bcodeFileHash: undefined | string;
331+
bcodeFileHash?: string;
332332
/**
333333
* Code lines in a given source file that have been optimized away.
334334
*/
335335
optimizedSrcLines: number[];
336336
/**
337337
* Code lines in a given disassembled bytecode file that have been optimized away.
338338
*/
339-
optimizedBcodeLines: undefined | number[];
339+
optimizedBcodeLines?: number[];
340340
/**
341341
* Name of the function represented by the frame.
342342
*/
@@ -348,7 +348,7 @@ interface ITraceGenFrameInfo {
348348
/**
349349
* Information for a given function in a disassembled byc file.
350350
*/
351-
bcodeFunEntry: undefined | IDebugInfoFunction;
351+
bcodeFunEntry?: IDebugInfoFunction;
352352
}
353353

354354
/**
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "stepping_dbg_info_2"
3+
edition = "2024.beta"
4+
5+
[dependencies]
6+
MoveStdlib = { local = "../../../../move-stdlib" }
7+
8+
[addresses]
9+
stepping_dbg_info_2 = "0x0"
10+
std = "0x1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":2,"from_file_path": "some/nonexistent/path","definition_location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":349,"end":350},"module_name":["0000000000000000000000000000000000000000000000000000000000000000","m"],"struct_map":{},"enum_map":{},"function_map":{"0":{"location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":353,"end":387},"definition_location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":357,"end":360},"type_parameters":[],"parameters":[["p#0#0",{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":361,"end":362}]],"returns":[{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":370,"end":373}],"locals":[],"nops":{},"code_map":{"0":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":380,"end":381},"1":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":384,"end":385},"2":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":382,"end":383},"3":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":380,"end":385}},"is_native":false},"1":{"location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":397,"end":556},"definition_location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":401,"end":405},"type_parameters":[],"parameters":[],"returns":[],"locals":[["_res#1#0",{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":422,"end":426}]],"nops":{},"code_map":{"0":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":433,"end":435},"1":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":429,"end":436},"2":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":418,"end":426},"3":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":449,"end":453},"4":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":460,"end":464},"5":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":456,"end":465},"6":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":454,"end":455},"7":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":442,"end":446},"8":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":478,"end":482},"9":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":489,"end":493},"10":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":485,"end":494},"11":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":483,"end":484},"12":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":471,"end":475},"13":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":494,"end":495}},"is_native":false},"2":{"location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":321,"end":556},"definition_location":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":321,"end":556},"type_parameters":[],"parameters":[],"returns":[],"locals":[],"nops":{},"code_map":{"0":{"file_hash":[143,173,198,181,146,141,90,55,178,38,100,160,9,39,15,6,83,178,250,132,117,135,106,3,48,249,152,61,147,142,60,210],"start":321,"end":556}},"is_native":false}},"constant_map":{}}

0 commit comments

Comments
 (0)