Skip to content

Commit 99d358a

Browse files
孙先富-技术研发BernardXiong
authored andcommitted
feat: 增加分析elf文件功能
1 parent 2af5f2e commit 99d358a

File tree

19 files changed

+6385
-2006
lines changed

19 files changed

+6385
-2006
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ ref/
1111
# Auto-generated type declaration files
1212
src/vue/auto-imports.d.ts
1313
src/vue/components.d.ts
14+
15+
rtthread.elf
16+
rtthread.map

package-lock.json

Lines changed: 5338 additions & 1987 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,11 @@
186186
"test": "vscode-test"
187187
},
188188
"dependencies": {
189+
"@bugsplat/elfy": "^1.0.1",
189190
"@vue/shared": "^3.x.x",
190191
"element-plus": "^2.4.2",
192+
"elfjs": "^2.2.0",
193+
"elfy": "^1.0.0",
191194
"marked": "^14.1.3",
192195
"vue": "^3.3.8",
193196
"vue-router": "^4.5.0"
@@ -197,8 +200,11 @@
197200
],
198201
"devDependencies": {
199202
"@types/mocha": "^10.0.7",
200-
"@types/node": "20.x",
203+
"@types/node": "^20.19.11",
201204
"@types/vscode": "^1.96.0",
205+
"@types/jest": "^29.5.12",
206+
"jest": "^29.7.0",
207+
"ts-jest": "^29.1.2",
202208
"@typescript-eslint/eslint-plugin": "^8.3.0",
203209
"@typescript-eslint/parser": "^8.3.0",
204210
"@vitejs/plugin-vue": "^5.2.1",
@@ -208,7 +214,8 @@
208214
"less": "^4.2.2",
209215
"svgo": "^4.0.0",
210216
"terser": "^5.43.1",
211-
"typescript": "^5.5.4",
217+
"ts-node": "^10.9.2",
218+
"typescript": "^5.8.3",
212219
"unplugin-auto-import": "^19.3.0",
213220
"unplugin-vue-components": "^28.8.0",
214221
"vite": "^6.0.11",

src/cmds/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,4 @@ export let cmds: Object = {
115115
label: 'packages',
116116
isExpanded: false
117117
}
118-
}
118+
};

src/dock.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ class CmdTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
1414
getChildren(element?: vscode.TreeItem): vscode.ProviderResult<vscode.TreeItem[]> {
1515
const isRTT = isRTThreadProject();
1616
const isRTTWorksapce = isRTThreadWorksapce();
17-
if (isRTT != true && isRTTWorksapce != true) {
17+
if (isRTT !== true && isRTTWorksapce !== true) {
1818
console.log("not RT-Thread project or workspace, return empty tree item.");
1919
}
2020

21-
if (isRTThreadProject() != true && isRTThreadWorksapce() != true) {
21+
if (isRTThreadProject() !== true && isRTThreadWorksapce() !== true) {
2222
// only show Create Project and RT-Thread Setting command when not in RT-Thread project
2323
let createProject = new vscode.TreeItem("create project", vscode.TreeItemCollapsibleState.None);
2424
createProject.iconPath = new vscode.ThemeIcon("new-folder");
@@ -45,9 +45,18 @@ class CmdTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
4545
command: "extension.showAbout",
4646
title: "show about page",
4747
arguments: [],
48-
}
48+
};
49+
50+
let analyze = new vscode.TreeItem("Analyze", vscode.TreeItemCollapsibleState.None);
51+
analyze.iconPath = new vscode.ThemeIcon("file");
52+
analyze.label = "Analyze";
53+
analyze.command = {
54+
command: "extension.showAnalyze",
55+
title: "show Analyze page",
56+
arguments: [],
57+
};
4958

50-
return [createProject, rtSetting, about];
59+
return [createProject, rtSetting, about, analyze];
5160
}
5261

5362
if (!element) {
@@ -89,25 +98,35 @@ class CmdTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
8998
command: "extension.showAbout",
9099
title: "show about page",
91100
arguments: [],
92-
}
101+
};
93102
children.push(about);
94103

104+
let analyze = new vscode.TreeItem("Analyze", vscode.TreeItemCollapsibleState.None);
105+
analyze.iconPath = new vscode.ThemeIcon("info");
106+
analyze.label = "Analyze";
107+
analyze.command = {
108+
command: "extension.showAnalyze",
109+
title: "show analyze page",
110+
arguments: [],
111+
};
112+
children.push(analyze);
113+
95114
return children;
96115
} else {
97116
let children:any = [];
98117

99118
const kconfig = vscode.extensions.getExtension('rt-thread.rt-thread-kconfig');
100119

101120
for (const [key, value] of Object.entries(cmds)) {
102-
if (element.label == value.label) {
121+
if (element.label === value.label) {
103122
for (const cmdItem of value.subcmds) {
104123
let item = new vscode.TreeItem(cmdItem.name);
105124
item.iconPath = new vscode.ThemeIcon(cmdItem.iconId);
106-
if (cmdItem.name == 'menuconfig' && kconfig != undefined) {
125+
if (cmdItem.name === 'menuconfig' && kconfig !== undefined) {
107126
item.command = {
108127
command: "rt-thread-kconfig.menuconfig.windows",
109128
title: cmdItem.cmd.title
110-
}
129+
};
111130
}
112131
else {
113132
item.command = {

src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as os from 'os';
77
import { openSettingWebview } from './webviews/setting';
88
import { openAboutWebview } from './webviews/about';
99
import { openCreateProjectWebview } from './webviews/create-project';
10+
import { openAnalyzeWebview } from './webviews/analyze';
1011
import { initOnDidChangeListener } from './listener';
1112
import { executeCommand, initTerminal } from './terminal';
1213
import { getMenuItems, getParallelBuildNumber } from './smart';
@@ -25,8 +26,6 @@ export function postMessageExtensionData(context: vscode.ExtensionContext, panel
2526
// 获取插件版本号(从 package.json 中读取)
2627
const extensionVersion = context.extension.packageJSON.version;
2728
const extensionNaeme = context.extension.packageJSON.name;
28-
// 例如:version 为 "1.0.0"
29-
console.log('插件版本号:', extensionVersion);
3029
panel.webview.postMessage({ version: extensionVersion, name: extensionNaeme });
3130
}
3231

@@ -125,6 +124,9 @@ export async function activate(context: vscode.ExtensionContext) {
125124
vscode.commands.registerCommand('extension.showAbout', () => {
126125
openAboutWebview(context);
127126
});
127+
vscode.commands.registerCommand('extension.showAnalyze', () => {
128+
openAnalyzeWebview(context);
129+
});
128130

129131
if (isRTThreadWorksapce) {
130132
vscode.commands.registerCommand('extension.showWorkspaceSettings', () => {

src/utils/elf/elf-analyzer.test.ts

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { ElfAnalyzer, Section, Symbol } from './index';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
4+
5+
describe('ElfAnalyzer', () => {
6+
let analyzer: ElfAnalyzer;
7+
const elfPath = path.join(__dirname, '..', 'rtthread.elf');
8+
const mapPath = path.join(__dirname, '..', 'rtthread.map');
9+
10+
beforeAll(() => {
11+
// Skip tests if files don't exist
12+
if (!fs.existsSync(elfPath) || !fs.existsSync(mapPath)) {
13+
console.warn('Test files not found. Skipping tests.');
14+
return;
15+
}
16+
analyzer = new ElfAnalyzer(elfPath, mapPath);
17+
});
18+
19+
describe('getSections()', () => {
20+
it('should return an array of sections', () => {
21+
if (!analyzer) return;
22+
23+
const sections = analyzer.getSections();
24+
25+
expect(Array.isArray(sections)).toBe(true);
26+
expect(sections.length).toBeGreaterThan(0);
27+
});
28+
29+
it('should return sections with required properties', () => {
30+
if (!analyzer) return;
31+
32+
const sections = analyzer.getSections();
33+
34+
sections.forEach(section => {
35+
expect(section).toHaveProperty('name');
36+
expect(section).toHaveProperty('address');
37+
expect(section).toHaveProperty('size');
38+
expect(section).toHaveProperty('needsLoad');
39+
40+
expect(typeof section.name).toBe('string');
41+
expect(typeof section.address).toBe('number');
42+
expect(typeof section.size).toBe('number');
43+
expect(typeof section.needsLoad).toBe('boolean');
44+
});
45+
});
46+
47+
it('should include common sections like .text', () => {
48+
if (!analyzer) return;
49+
50+
const sections = analyzer.getSections();
51+
const sectionNames = sections.map(s => s.name);
52+
53+
expect(sectionNames).toContain('.text');
54+
});
55+
});
56+
57+
describe('getSymbolsBySection()', () => {
58+
it('should return symbols for a valid section', () => {
59+
if (!analyzer) return;
60+
61+
const symbols = analyzer.getSymbolsBySection('.text');
62+
63+
expect(Array.isArray(symbols)).toBe(true);
64+
expect(symbols.length).toBeGreaterThan(0);
65+
});
66+
67+
it('should return symbols with required properties', () => {
68+
if (!analyzer) return;
69+
70+
const symbols = analyzer.getSymbolsBySection('.text');
71+
72+
symbols.forEach(symbol => {
73+
expect(symbol).toHaveProperty('name');
74+
expect(symbol).toHaveProperty('type');
75+
expect(symbol).toHaveProperty('address');
76+
expect(symbol).toHaveProperty('size');
77+
78+
expect(typeof symbol.name).toBe('string');
79+
expect(typeof symbol.type).toBe('string');
80+
expect(typeof symbol.address).toBe('number');
81+
expect(typeof symbol.size).toBe('number');
82+
});
83+
});
84+
85+
it('should return symbols sorted by size in descending order', () => {
86+
if (!analyzer) return;
87+
88+
const symbols = analyzer.getSymbolsBySection('.text');
89+
90+
for (let i = 1; i < symbols.length; i++) {
91+
expect(symbols[i].size).toBeLessThanOrEqual(symbols[i-1].size);
92+
}
93+
});
94+
95+
it('should return empty array for non-existent section', () => {
96+
if (!analyzer) return;
97+
98+
const symbols = analyzer.getSymbolsBySection('.nonexistent');
99+
100+
expect(Array.isArray(symbols)).toBe(true);
101+
expect(symbols.length).toBe(0);
102+
});
103+
});
104+
105+
describe('getAllSymbols()', () => {
106+
it('should return an array of all symbols', () => {
107+
if (!analyzer) return;
108+
109+
const symbols = analyzer.getAllSymbols();
110+
111+
expect(Array.isArray(symbols)).toBe(true);
112+
expect(symbols.length).toBeGreaterThan(0);
113+
});
114+
115+
it('should not include symbols with zero size', () => {
116+
if (!analyzer) return;
117+
118+
const symbols = analyzer.getAllSymbols();
119+
120+
symbols.forEach(symbol => {
121+
expect(symbol.size).toBeGreaterThan(0);
122+
});
123+
});
124+
125+
it('should return symbols sorted by size in descending order', () => {
126+
if (!analyzer) return;
127+
128+
const symbols = analyzer.getAllSymbols();
129+
130+
for (let i = 1; i < symbols.length; i++) {
131+
expect(symbols[i].size).toBeLessThanOrEqual(symbols[i-1].size);
132+
}
133+
});
134+
135+
it('should return symbols with valid addresses', () => {
136+
if (!analyzer) return;
137+
138+
const symbols = analyzer.getAllSymbols();
139+
140+
symbols.forEach(symbol => {
141+
expect(symbol.address).toBeGreaterThanOrEqual(0);
142+
expect(Number.isInteger(symbol.address)).toBe(true);
143+
});
144+
});
145+
});
146+
147+
describe('getSymbols()', () => {
148+
it('should return symbols for a specific object file', () => {
149+
if (!analyzer) return;
150+
151+
// First get all symbols to find an object file
152+
const allSymbols = analyzer.getAllSymbols();
153+
const objectFiles = new Set<string>();
154+
155+
allSymbols.forEach(s => {
156+
if (s.object) {
157+
objectFiles.add(s.object);
158+
}
159+
});
160+
161+
if (objectFiles.size > 0) {
162+
const testObject = Array.from(objectFiles)[0];
163+
const symbols = analyzer.getSymbols(testObject);
164+
165+
expect(Array.isArray(symbols)).toBe(true);
166+
167+
// All returned symbols should belong to the specified object
168+
symbols.forEach(symbol => {
169+
expect(symbol.object).toBe(testObject);
170+
});
171+
}
172+
});
173+
174+
it('should return symbols sorted by size in descending order', () => {
175+
if (!analyzer) return;
176+
177+
const allSymbols = analyzer.getAllSymbols();
178+
const objectFiles = new Set<string>();
179+
180+
allSymbols.forEach(s => {
181+
if (s.object) {
182+
objectFiles.add(s.object);
183+
}
184+
});
185+
186+
if (objectFiles.size > 0) {
187+
const testObject = Array.from(objectFiles)[0];
188+
const symbols = analyzer.getSymbols(testObject);
189+
190+
for (let i = 1; i < symbols.length; i++) {
191+
expect(symbols[i].size).toBeLessThanOrEqual(symbols[i-1].size);
192+
}
193+
}
194+
});
195+
196+
it('should return empty array for non-existent object', () => {
197+
if (!analyzer) return;
198+
199+
const symbols = analyzer.getSymbols('nonexistent.o');
200+
201+
expect(Array.isArray(symbols)).toBe(true);
202+
expect(symbols.length).toBe(0);
203+
});
204+
});
205+
206+
describe('Error handling', () => {
207+
it('should throw error when accessing getSections without ELF file', () => {
208+
const noElfAnalyzer = new ElfAnalyzer(undefined, mapPath);
209+
210+
expect(() => {
211+
noElfAnalyzer.getSections();
212+
}).toThrow('ELF file not loaded');
213+
});
214+
215+
it('should throw error when accessing getSymbolsBySection without ELF file', () => {
216+
const noElfAnalyzer = new ElfAnalyzer(undefined, mapPath);
217+
218+
expect(() => {
219+
noElfAnalyzer.getSymbolsBySection('.text');
220+
}).toThrow('ELF file not loaded');
221+
});
222+
223+
it('should throw error when accessing getAllSymbols without any file', () => {
224+
const emptyAnalyzer = new ElfAnalyzer();
225+
226+
expect(() => {
227+
emptyAnalyzer.getAllSymbols();
228+
}).toThrow('No ELF or MAP file loaded');
229+
});
230+
});
231+
});

0 commit comments

Comments
 (0)