Skip to content

Commit dd989ea

Browse files
authored
Add support for Rust v0 symbol mangling scheme (#491)
1 parent c04a148 commit dd989ea

15 files changed

+226
-54
lines changed

src/lib/demangle-cpp.test.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/lib/demangle-cpp.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/lib/demangle/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gcc

src/lib/demangle/Makefile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
EMCC = emcc
2+
CFLAGS = -Os -Igcc/include -DHAVE_STDLIB_H -DHAVE_STRING_H
3+
LDFLAGS_COMMON = \
4+
-s EXPORTED_RUNTIME_METHODS=stringToUTF8OnStack,UTF8ToString \
5+
-s EXPORTED_FUNCTIONS=_demangle,_free \
6+
-s MODULARIZE=1 \
7+
-s WASM=1 \
8+
-s FILESYSTEM=0 \
9+
-s MINIMAL_RUNTIME=1
10+
11+
# We have to disable EXPORT_ES6 as otherwise since esbuild cannot transpile that
12+
# into umd or anything else.
13+
# .ts files throughout the project can do this just fine because they are
14+
# transpiled to compatible js.
15+
ifeq ($(TEST),1)
16+
LDFLAGS = $(LDFLAGS_COMMON) -s EXPORT_ES6=1
17+
else
18+
# SINGLE_FILE=1 embeds the wasm as base64.
19+
LDFLAGS = $(LDFLAGS_COMMON) -s ASSERTIONS=0 -s SINGLE_FILE=1 -s ENVIRONMENT=web
20+
endif
21+
22+
SRC_FILES = \
23+
gcc/libiberty/safe-ctype.c \
24+
gcc/libiberty/rust-demangle.c \
25+
gcc/libiberty/cp-demangle.c \
26+
demangle.c
27+
POST_JS = demangle.post.js
28+
EXTERN_POST_JS = demangle.extern.post.js
29+
OUTPUT = demangle.wasm.js
30+
31+
all: $(OUTPUT)
32+
33+
$(OUTPUT): $(SRC_FILES) $(POST_JS)
34+
$(EMCC) $(CFLAGS) $(SRC_FILES) $(LDFLAGS) --post-js $(POST_JS) --extern-post-js $(EXTERN_POST_JS) --no-entry -o $@
35+
36+
clean:
37+
rm -f $(OUTPUT)
38+
39+
.PHONY: all clean

src/lib/demangle/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# demangle
2+
3+
A wrapper function on top of demangling functions from the `GNU libiberty`,
4+
using emscripten.
5+
6+
# Build dependencies
7+
8+
## emscripten 4.0.0
9+
10+
Follow the official `emsdk` installation instructions:
11+
12+
https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended
13+
14+
And make sure you have `emcc` in your PATH.
15+
16+
# Source dependencies
17+
18+
## GCC
19+
20+
Make sure to fetch `gcc` sources.
21+
22+
* `git clone https://github.com/gcc-mirror/gcc`
23+
* `git reset --hard 40754a3b9bef83bf4da0675fcb378e8cd1675602`
24+
25+
# Build instructions
26+
27+
`make` to produce a single CommonJS module that contains also contain the base64 encoded wasm file.
28+
`make TEST=1` to produce both a ES6 module AND the wasm file.
29+
30+
Using `make TEST=1` produce a file that can be used by `node` for testing purposes.

src/lib/demangle/demangle.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "gcc/include/demangle.h"
2+
3+
#include <string.h>
4+
5+
static char *non_microsoft_demangle(const char *mangled) {
6+
int is_itanium_symbol = strncmp(mangled, "_Z", 2) == 0;
7+
if (is_itanium_symbol) {
8+
// Note: __cxa_demangle default is DMGL_PARAMS | DMGL_TYPES
9+
return cplus_demangle_v3(mangled, DMGL_PARAMS | DMGL_TYPES);
10+
}
11+
12+
int is_rust_symbol = strncmp(mangled, "_R", 2) == 0;
13+
if (is_rust_symbol) {
14+
// Note: rust_demangle uses only DMGL_VERBOSE and DMGL_NO_RECURSE_LIMIT,
15+
// so no need to pass any options in our case.
16+
return rust_demangle(mangled, DMGL_NO_OPTS);
17+
}
18+
19+
return NULL;
20+
}
21+
22+
// Logic is inspired by llvm::demangle.
23+
// It is the caller's responsibility to free the string which is returned.
24+
char *demangle(const char *mangled) {
25+
char *demangled = non_microsoft_demangle(mangled);
26+
if (demangled) {
27+
return demangled;
28+
}
29+
30+
if (mangled[0] == '_') {
31+
demangled = non_microsoft_demangle(&mangled[1]);
32+
if (demangled) {
33+
return demangled;
34+
}
35+
}
36+
37+
return NULL;
38+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* DO NOT USE THIS FILE DIRECTLY.
3+
*
4+
* This file is only used as --extern-post-js of emcc.
5+
*/
6+
module.exports = Module
7+
module.exports.default = Module

src/lib/demangle/demangle.post.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* DO NOT USE THIS FILE DIRECTLY.
3+
*
4+
* This file is only used as --post-js of emcc.
5+
*
6+
* This file provides a higher level demangle function ready to use
7+
* in JavaScript.
8+
*/
9+
Module['wasm_demangle'] = function(mangled) {
10+
/*
11+
* We are manually calling the lower-level generated functions
12+
* instead of using `cwrap` because we need to `free` the pointer
13+
* returned by `_demangle`.
14+
*/
15+
const param_ptr = stringToUTF8OnStack(mangled);
16+
const result_ptr = _demangle(param_ptr);
17+
const result = UTF8ToString(result_ptr);
18+
if (result_ptr !== null && result_ptr !== undefined) {
19+
_free(result_ptr);
20+
}
21+
return result;
22+
}

src/lib/demangle/demangle.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {loadDemangling} from './demangle'
2+
3+
test('demangle', async () => {
4+
const demangle = await loadDemangling()
5+
6+
expect(demangle('a')).toBe('a')
7+
expect(demangle('someUnobfuscatedFunction')).toBe('someUnobfuscatedFunction')
8+
9+
// C++ mangling
10+
expect(demangle('__ZNK7Support6ColorFeqERKS0_')).toBe(
11+
'Support::ColorF::operator==(Support::ColorF const&) const',
12+
)
13+
// Running a second time to test the cache
14+
expect(demangle('__ZNK7Support6ColorFeqERKS0_')).toBe(
15+
'Support::ColorF::operator==(Support::ColorF const&) const',
16+
)
17+
18+
// Rust v0 mangling
19+
expect(demangle('_RNvCskwGfYPst2Cb_3foo16example_function')).toBe('foo::example_function')
20+
21+
// Rust legacy mangling
22+
expect(demangle('_ZN3std2fs8Metadata7created17h8df207f105c5d474E')).toBe(
23+
'std::fs::Metadata::created::h8df207f105c5d474',
24+
)
25+
26+
// False positive
27+
expect(demangle('_ZoomIn')).toBe('_ZoomIn')
28+
})

src/lib/demangle/demangle.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import createWasmDemangleModule from './demangle.wasm'
2+
3+
const wasmDemangleModulePromise = createWasmDemangleModule().then(module => module)
4+
5+
const cache = new Map<string, string>()
6+
7+
export async function loadDemangling(): Promise<(name: string) => string> {
8+
// This function converts a mangled C++ name such as "__ZNK7Support6ColorFeqERKS0_"
9+
// into a human-readable symbol (in this case "Support::ColorF::==(Support::ColorF&)")
10+
const wasmDemangleModule = await wasmDemangleModulePromise
11+
return cached(wasmDemangleModule.wasm_demangle)
12+
}
13+
14+
function cached(demangle: (name: string) => string): (name: string) => string {
15+
return (name: string): string => {
16+
let result = cache.get(name)
17+
if (result !== undefined) {
18+
name = result
19+
} else {
20+
result = demangle(name)
21+
result = result === '' ? name : result
22+
cache.set(name, result)
23+
name = result
24+
}
25+
return name
26+
}
27+
}

src/lib/demangle/demangle.wasm.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
interface WasmDemangleModule {
2+
wasm_demangle(mangled: string): string
3+
}
4+
5+
export default function ModuleFactory(options?: unknown): Promise<WasmDemangleModule>

src/lib/demangle/demangle.wasm.js

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

src/lib/demangle/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {loadDemangling} from './demangle'

src/lib/profile.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {lastOf, KeyedSet} from './utils'
22
import {ValueFormatter, RawValueFormatter} from './value-formatters'
33
import {FileFormat} from './file-format-spec'
4-
const demangleCppModule = import('./demangle-cpp')
54

65
export interface FrameInfo {
76
key: string | number
@@ -404,16 +403,20 @@ export class Profile {
404403

405404
// Demangle symbols for readability
406405
async demangle() {
407-
let demangleCpp: ((name: string) => string) | null = null
406+
let demangle: ((name: string) => string) | null = null
408407

409408
for (let frame of this.frames) {
410-
// This function converts a mangled C++ name such as "__ZNK7Support6ColorFeqERKS0_"
411-
// into a human-readable symbol (in this case "Support::ColorF::==(Support::ColorF&)")
412-
if (frame.name.startsWith('__Z')) {
413-
if (!demangleCpp) {
414-
demangleCpp = (await demangleCppModule).demangleCpp
409+
// This function converts a mangled C++ and Rust name into a human-readable symbol.
410+
if (
411+
frame.name.startsWith('__Z') ||
412+
frame.name.startsWith('_R') ||
413+
frame.name.startsWith('_Z')
414+
) {
415+
if (!demangle) {
416+
const demangleModule = await import('./demangle')
417+
demangle = await demangleModule.loadDemangling()
415418
}
416-
frame.name = demangleCpp(frame.name)
419+
frame.name = demangle(frame.name)
417420
}
418421
}
419422
}

src/views/application.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const importModule = import('../import')
2828
// We put them all in one place so we can directly control the relative priority
2929
// of these.
3030
importModule.then(() => {})
31-
import('../lib/demangle-cpp').then(() => {})
31+
import('../lib/demangle').then(() => {})
3232
import('source-map').then(() => {})
3333

3434
async function importProfilesFromText(

0 commit comments

Comments
 (0)