[vm] Add symbol size and type information to the assembly output.

Add symbol size and type information to the assembly output when
compiling for Linux-based platforms, so that the symbol tables in the
assembled output include that information.

Since symbol tables in Mach-O files do not include symbol size
information, we don't output either currently for MacOS or iOS targets.

TEST=vm/dart{,_2}/use_add_readonly_data_symbols_flag

Change-Id: I4219b898249153dc84214565e85ac9d3cf802538
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-gcc-linux-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260820
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Tess Strickland 2022-09-27 16:45:53 +00:00 committed by Commit Queue
parent 823934f1c5
commit 1d081c8bdd
4 changed files with 199 additions and 52 deletions

View file

@ -779,24 +779,72 @@ class StringTable extends Section implements DwarfContainerStringTable {
/// An enumeration of recognized symbol binding values used by the ELF format. /// An enumeration of recognized symbol binding values used by the ELF format.
enum SymbolBinding { enum SymbolBinding {
STB_LOCAL, // We only list the standard types here, not OS-specific ones.
STB_GLOBAL, STB_LOCAL(0, 'local'),
STB_WEAK, STB_GLOBAL(1, 'global'),
STB_WEAK(2, 'weak');
final int code;
final String description;
const SymbolBinding(this.code, this.description);
static SymbolBinding? fromCode(int code) {
for (final value in values) {
if (value.code == code) {
return value;
}
}
return null;
}
} }
/// An enumeration of recognized symbol types used by the ELF format. /// An enumeration of recognized symbol types used by the ELF format.
enum SymbolType { enum SymbolType {
STT_NOTYPE, // We only list the standard types here, not OS-specific ones.
STT_OBJECT, STT_NOTYPE(0, 'notype'),
STT_FUNC, STT_OBJECT(1, 'object'),
STT_SECTION, STT_FUNC(2, 'function'),
STT_SECTION(3, 'section'),
STT_FILE(4, 'file'),
STT_COMMON(5, 'common'),
STT_TLS(6, 'thread-local');
final int code;
final String description;
const SymbolType(this.code, this.description);
static SymbolType? fromCode(int code) {
for (final value in values) {
if (value.code == code) {
return value;
}
}
return null;
}
} }
enum SymbolVisibility { enum SymbolVisibility {
STV_DEFAULT, // We only list the standard values here.
STV_INTERNAL, STV_DEFAULT(0, 'public'),
STV_HIDDEN, STV_INTERNAL(1, 'internal'),
STV_PROTECTED, STV_HIDDEN(2, 'hidden'),
STV_PROTECTED(3, 'protected');
final int code;
final String description;
const SymbolVisibility(this.code, this.description);
static SymbolVisibility? fromCode(int code) {
for (final value in values) {
if (value.code == code) {
return value;
}
}
return null;
}
} }
/// A symbol in an ELF file, which names a portion of the virtual address space. /// A symbol in an ELF file, which names a portion of the virtual address space.
@ -837,40 +885,20 @@ class Symbol implements DwarfContainerSymbol {
nameIndex, info, other, sectionIndex, value, size, wordSize); nameIndex, info, other, sectionIndex, value, size, wordSize);
} }
SymbolBinding get bind => SymbolBinding.values[info >> 4]; SymbolBinding? get bind => SymbolBinding.fromCode(info >> 4);
SymbolType get type => SymbolType.values[info & 0x0f]; SymbolType? get type => SymbolType.fromCode(info & 0x0f);
SymbolVisibility get visibility => SymbolVisibility.values[other & 0x03]; SymbolVisibility? get visibility => SymbolVisibility.fromCode(other & 0x03);
void writeToStringBuffer(StringBuffer buffer) { void writeToStringBuffer(StringBuffer buffer) {
buffer buffer
..write('"') ..write('"')
..write(name) ..write(name)
..write('" =>'); ..write('" => a ')
switch (bind) { ..write(bind?.description ?? '<binding unrecognized>')
case SymbolBinding.STB_GLOBAL: ..write(' ')
buffer.write(' a global'); ..write(type?.description ?? '<type unrecognized>')
break; ..write(' ')
case SymbolBinding.STB_LOCAL: ..write(visibility?.description ?? '<visibility unrecognized>')
buffer.write(' a local');
break;
case SymbolBinding.STB_WEAK:
buffer.write(' a weak');
break;
}
switch (visibility) {
case SymbolVisibility.STV_DEFAULT:
break;
case SymbolVisibility.STV_HIDDEN:
buffer.write(' hidden');
break;
case SymbolVisibility.STV_INTERNAL:
buffer.write(' internal');
break;
case SymbolVisibility.STV_PROTECTED:
buffer.write(' protected');
break;
}
buffer
..write(' symbol that points to ') ..write(' symbol that points to ')
..write(size) ..write(size)
..write(' bytes at location 0x') ..write(' bytes at location 0x')

View file

@ -64,15 +64,38 @@ main(List<String> args) async {
checkElf(scriptSnapshot); checkElf(scriptSnapshot);
checkElf(scriptDebuggingInfo); checkElf(scriptDebuggingInfo);
if (Platform.isLinux) {
final scriptAssembly = path.join(tempDir, 'snapshot.S');
final scriptAssemblySnapshot = path.join(tempDir, 'assembly.so');
final scriptAssemblyDebuggingInfo =
path.join(tempDir, 'assembly_debug.so');
await run(genSnapshot, <String>[
'--add-readonly-data-symbols',
'--dwarf-stack-traces-mode',
'--save-debugging-info=$scriptAssemblyDebuggingInfo',
'--snapshot-kind=app-aot-assembly',
'--assembly=$scriptAssembly',
scriptDill,
]);
await assembleSnapshot(scriptAssembly, scriptAssemblySnapshot,
debug: true);
checkElf(scriptAssemblySnapshot, isAssembled: true);
checkElf(scriptAssemblyDebuggingInfo);
}
}); });
} }
void checkElf(String filename) { void checkElf(String filename, {bool isAssembled = false}) {
// Check that the static symbol table contains entries that are not in the // Check that the static symbol table contains entries that are not in the
// dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT. // dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT.
final elf = Elf.fromFile(filename); final elf = Elf.fromFile(filename);
Expect.isNotNull(elf); Expect.isNotNull(elf);
final dynamicSymbols = elf!.dynamicSymbols.toList(); final dynamicSymbols = elf!.dynamicSymbols.toList();
print('Dynamic symbols:');
for (final symbol in dynamicSymbols) { for (final symbol in dynamicSymbols) {
// All symbol tables have an initial entry with zero-valued fields. // All symbol tables have an initial entry with zero-valued fields.
if (symbol.name == '') { if (symbol.name == '') {
@ -80,23 +103,43 @@ void checkElf(String filename) {
Expect.equals(SymbolType.STT_NOTYPE, symbol.type); Expect.equals(SymbolType.STT_NOTYPE, symbol.type);
Expect.equals(0, symbol.value); Expect.equals(0, symbol.value);
} else { } else {
print(symbol);
if (!symbol.name.startsWith('_kDart')) {
// The VM only adds symbols with names starting with _kDart, so this
// must be an assembled snapshot.
Expect.isTrue(isAssembled);
continue;
}
Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind); Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind);
Expect.equals(SymbolType.STT_OBJECT, symbol.type); Expect.equals(SymbolType.STT_OBJECT, symbol.type);
Expect.isTrue(symbol.name.startsWith('_kDart'), // All VM-generated read-only object symbols should have a non-zero size.
'unexpected symbol name ${symbol.name}'); Expect.notEquals(0, symbol.size);
} }
} }
print("");
final onlyStaticSymbols = elf.staticSymbols final onlyStaticSymbols = elf.staticSymbols
.where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name)); .where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name));
Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols'); Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols');
final objectSymbols = final objectSymbols =
onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT); onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT);
Expect.isNotEmpty(objectSymbols, 'no static-only object symbols'); Expect.isNotEmpty(objectSymbols, 'no static-only object symbols');
print("Static-only object symbols:");
for (final symbol in objectSymbols) { for (final symbol in objectSymbols) {
// Currently we only write local object symbols. print(symbol);
// There should be no static-only global object symbols.
Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind);
// All object symbols are prefixed with the type of the C++ object. final objectTypeEnd = symbol.name.indexOf('_');
final objectType = symbol.name.substring(0, symbol.name.indexOf('_')); // All VM-generated read-only object symbols are prefixed with the type of
// the C++ object followed by an underscore. If assembling the snapshot,
// the assembler might introduce other object symbols which either start
// with an underscore or have no underscore.
if (objectTypeEnd <= 0) {
Expect.isTrue(isAssembled);
continue;
}
// All VM-generated read-only object symbols should have a non-zero size.
Expect.notEquals(0, symbol.size);
final objectType = symbol.name.substring(0, objectTypeEnd);
switch (objectType) { switch (objectType) {
// Used for entries in the non-clustered portion of the read-only data // Used for entries in the non-clustered portion of the read-only data
// section that don't correspond to a specific Dart object. // section that don't correspond to a specific Dart object.

View file

@ -66,15 +66,38 @@ main(List<String> args) async {
checkElf(scriptSnapshot); checkElf(scriptSnapshot);
checkElf(scriptDebuggingInfo); checkElf(scriptDebuggingInfo);
if (Platform.isLinux) {
final scriptAssembly = path.join(tempDir, 'snapshot.S');
final scriptAssemblySnapshot = path.join(tempDir, 'assembly.so');
final scriptAssemblyDebuggingInfo =
path.join(tempDir, 'assembly_debug.so');
await run(genSnapshot, <String>[
'--add-readonly-data-symbols',
'--dwarf-stack-traces-mode',
'--save-debugging-info=$scriptAssemblyDebuggingInfo',
'--snapshot-kind=app-aot-assembly',
'--assembly=$scriptAssembly',
scriptDill,
]);
await assembleSnapshot(scriptAssembly, scriptAssemblySnapshot,
debug: true);
checkElf(scriptAssemblySnapshot, isAssembled: true);
checkElf(scriptAssemblyDebuggingInfo);
}
}); });
} }
void checkElf(String filename) { void checkElf(String filename, {bool isAssembled = false}) {
// Check that the static symbol table contains entries that are not in the // Check that the static symbol table contains entries that are not in the
// dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT. // dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT.
final elf = Elf.fromFile(filename); final elf = Elf.fromFile(filename);
Expect.isNotNull(elf); Expect.isNotNull(elf);
final dynamicSymbols = elf.dynamicSymbols.toList(); final dynamicSymbols = elf.dynamicSymbols.toList();
print('Dynamic symbols:');
for (final symbol in dynamicSymbols) { for (final symbol in dynamicSymbols) {
// All symbol tables have an initial entry with zero-valued fields. // All symbol tables have an initial entry with zero-valued fields.
if (symbol.name == '') { if (symbol.name == '') {
@ -82,23 +105,43 @@ void checkElf(String filename) {
Expect.equals(SymbolType.STT_NOTYPE, symbol.type); Expect.equals(SymbolType.STT_NOTYPE, symbol.type);
Expect.equals(0, symbol.value); Expect.equals(0, symbol.value);
} else { } else {
print(symbol);
if (!symbol.name.startsWith('_kDart')) {
// The VM only adds symbols with names starting with _kDart, so this
// must be an assembled snapshot.
Expect.isTrue(isAssembled);
continue;
}
Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind); Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind);
Expect.equals(SymbolType.STT_OBJECT, symbol.type); Expect.equals(SymbolType.STT_OBJECT, symbol.type);
Expect.isTrue(symbol.name.startsWith('_kDart'), // All VM-generated read-only object symbols should have a non-zero size.
'unexpected symbol name ${symbol.name}'); Expect.notEquals(0, symbol.size);
} }
} }
print("");
final onlyStaticSymbols = elf.staticSymbols final onlyStaticSymbols = elf.staticSymbols
.where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name)); .where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name));
Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols'); Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols');
final objectSymbols = final objectSymbols =
onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT); onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT);
Expect.isNotEmpty(objectSymbols, 'no static-only object symbols'); Expect.isNotEmpty(objectSymbols, 'no static-only object symbols');
print("Static-only object symbols:");
for (final symbol in objectSymbols) { for (final symbol in objectSymbols) {
// Currently we only write local object symbols. print(symbol);
// There should be no static-only global object symbols.
Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind);
// All object symbols are prefixed with the type of the C++ object. final objectTypeEnd = symbol.name.indexOf('_');
final objectType = symbol.name.substring(0, symbol.name.indexOf('_')); // All VM-generated read-only object symbols are prefixed with the type of
// the C++ object followed by an underscore. If assembling the snapshot,
// the assembler might introduce other object symbols which either start
// with an underscore or have no underscore.
if (objectTypeEnd <= 0) {
Expect.isTrue(isAssembled);
continue;
}
// All VM-generated read-only object symbols should have a non-zero size.
Expect.notEquals(0, symbol.size);
final objectType = symbol.name.substring(0, objectTypeEnd);
switch (objectType) { switch (objectType) {
// Used for entries in the non-clustered portion of the read-only data // Used for entries in the non-clustered portion of the read-only data
// section that don't correspond to a specific Dart object. // section that don't correspond to a specific Dart object.

View file

@ -1294,6 +1294,17 @@ void AssemblyImageWriter::WriteROData(NonStreamingWriteStream* clustered_stream,
for (const auto& symbol : *current_symbols_) { for (const auto& symbol : *current_symbols_) {
WriteBytes(bytes + last_position, symbol.offset - last_position); WriteBytes(bytes + last_position, symbol.offset - last_position);
assembly_stream_->Printf("%s:\n", symbol.name); assembly_stream_->Printf("%s:\n", symbol.name);
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \
defined(DART_TARGET_OS_FUCHSIA)
// Output size and type of the read-only data symbol to the assembly stream.
assembly_stream_->Printf(".size %s, %zu\n", symbol.name, symbol.size);
assembly_stream_->Printf(".type %s, %%object\n", symbol.name);
#elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
// MachO symbol tables don't include the size of the symbol, so don't bother
// printing it to the assembly output.
#else
UNIMPLEMENTED();
#endif
last_position = symbol.offset; last_position = symbol.offset;
} }
WriteBytes(bytes + last_position, len - last_position); WriteBytes(bytes + last_position, len - last_position);
@ -1379,6 +1390,17 @@ void AssemblyImageWriter::ExitSection(ProgramSection name,
// We should still be in the same section as the last EnterSection. // We should still be in the same section as the last EnterSection.
ASSERT(current_section_symbol_ != nullptr); ASSERT(current_section_symbol_ != nullptr);
ASSERT_EQUAL(strcmp(SectionSymbol(name, vm), current_section_symbol_), 0); ASSERT_EQUAL(strcmp(SectionSymbol(name, vm), current_section_symbol_), 0);
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \
defined(DART_TARGET_OS_FUCHSIA)
// Output the size of the section symbol to the assembly stream.
assembly_stream_->Printf(".size %s, %zu\n", current_section_symbol_, size);
assembly_stream_->Printf(".type %s, %%object\n", current_section_symbol_);
#elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
// MachO symbol tables don't include the size of the symbol, so don't bother
// printing it to the assembly output.
#else
UNIMPLEMENTED();
#endif
// We need to generate a text segment of the appropriate size in the ELF // We need to generate a text segment of the appropriate size in the ELF
// for two reasons: // for two reasons:
// //
@ -1460,6 +1482,17 @@ void AssemblyImageWriter::AddCodeSymbol(const Code& code,
debug_elf_->dwarf()->AddCode(code, symbol); debug_elf_->dwarf()->AddCode(code, symbol);
} }
assembly_stream_->Printf("%s:\n", symbol); assembly_stream_->Printf("%s:\n", symbol);
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \
defined(DART_TARGET_OS_FUCHSIA)
// Output the size of the code symbol to the assembly stream.
assembly_stream_->Printf(".size %s, %zu\n", symbol, code.Size());
assembly_stream_->Printf(".type %s, %%function\n", symbol);
#elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
// MachO symbol tables don't include the size of the symbol, so don't bother
// printing it to the assembly output.
#else
UNIMPLEMENTED();
#endif
} }
void AssemblyImageWriter::AddDataSymbol(const char* symbol, void AssemblyImageWriter::AddDataSymbol(const char* symbol,