Skip to content

Commit c8daf4a

Browse files
alvinhochunmstorsjo
authored andcommitted
[lldb] Add gnu-debuglink support for Windows PE/COFF
The specification of gnu-debuglink can be found at: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html The file CRC or the CRC value from the .gnu_debuglink section is now used to calculate the module UUID as a fallback, to allow verifying that the debug object does match the executable. Note that if a CodeView build id exists, it still takes precedence. This works even for MinGW builds because LLD writes a synthetic CodeView build id which does not get stripped from the debug object. The `Minidump/Windows/find-module` test also needs a fix by adding a CodeView record to the exe to match the one in the minidump, otherwise it fails due to the new UUID calculated from the file CRC. Fixes llvm#54344 Reviewed By: DavidSpickett Differential Revision: https://reviews.llvm.org/D126367
1 parent 264d113 commit c8daf4a

File tree

12 files changed

+503
-11
lines changed

12 files changed

+503
-11
lines changed

lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "llvm/BinaryFormat/COFF.h"
3232

3333
#include "llvm/Object/COFFImportFile.h"
34+
#include "llvm/Support/CRC.h"
3435
#include "llvm/Support/Error.h"
3536
#include "llvm/Support/MemoryBuffer.h"
3637

@@ -44,10 +45,42 @@ using namespace lldb_private;
4445

4546
LLDB_PLUGIN_DEFINE(ObjectFilePECOFF)
4647

48+
static bool GetDebugLinkContents(const llvm::object::COFFObjectFile &coff_obj,
49+
std::string &gnu_debuglink_file,
50+
uint32_t &gnu_debuglink_crc) {
51+
static ConstString g_sect_name_gnu_debuglink(".gnu_debuglink");
52+
for (const auto &section : coff_obj.sections()) {
53+
auto name = section.getName();
54+
if (!name) {
55+
llvm::consumeError(name.takeError());
56+
continue;
57+
}
58+
if (*name == g_sect_name_gnu_debuglink.GetStringRef()) {
59+
auto content = section.getContents();
60+
if (!content) {
61+
llvm::consumeError(content.takeError());
62+
return false;
63+
}
64+
DataExtractor data(
65+
content->data(), content->size(),
66+
coff_obj.isLittleEndian() ? eByteOrderLittle : eByteOrderBig, 4);
67+
lldb::offset_t gnu_debuglink_offset = 0;
68+
gnu_debuglink_file = data.GetCStr(&gnu_debuglink_offset);
69+
// Align to the next 4-byte offset
70+
gnu_debuglink_offset = llvm::alignTo(gnu_debuglink_offset, 4);
71+
data.GetU32(&gnu_debuglink_offset, &gnu_debuglink_crc, 1);
72+
return true;
73+
}
74+
}
75+
return false;
76+
}
77+
4778
static UUID GetCoffUUID(llvm::object::COFFObjectFile &coff_obj) {
4879
const llvm::codeview::DebugInfo *pdb_info = nullptr;
4980
llvm::StringRef pdb_file;
5081

82+
// First, prefer to use the PDB build id. LLD generates this even for mingw
83+
// targets without PDB output, and it does not get stripped either.
5184
if (!coff_obj.getDebugPDBInfo(pdb_info, pdb_file) && pdb_info) {
5285
if (pdb_info->PDB70.CVSignature == llvm::OMF::Signature::PDB70) {
5386
UUID::CvRecordPdb70 info;
@@ -57,7 +90,26 @@ static UUID GetCoffUUID(llvm::object::COFFObjectFile &coff_obj) {
5790
}
5891
}
5992

60-
return UUID();
93+
std::string gnu_debuglink_file;
94+
uint32_t gnu_debuglink_crc;
95+
96+
// The GNU linker normally does not write a PDB build id (unless requested
97+
// with the --build-id option), so we should fall back to using the crc
98+
// from .gnu_debuglink if it exists, just like how ObjectFileELF does it.
99+
if (!GetDebugLinkContents(coff_obj, gnu_debuglink_file, gnu_debuglink_crc)) {
100+
// If there is no .gnu_debuglink section, then this may be an object
101+
// containing DWARF debug info for .gnu_debuglink, so calculate the crc of
102+
// the object itself.
103+
auto raw_data = coff_obj.getData();
104+
LLDB_SCOPED_TIMERF(
105+
"Calculating module crc32 %s with size %" PRIu64 " KiB",
106+
FileSpec(coff_obj.getFileName()).GetLastPathComponent().AsCString(),
107+
static_cast<lldb::offset_t>(raw_data.size()) / 1024);
108+
gnu_debuglink_crc = llvm::crc32(0, llvm::arrayRefFromStringRef(raw_data));
109+
}
110+
// Use 4 bytes of crc from the .gnu_debuglink section.
111+
llvm::support::ulittle32_t data(gnu_debuglink_crc);
112+
return UUID::fromData(&data, sizeof(data));
61113
}
62114

63115
char ObjectFilePECOFF::ID;
@@ -870,6 +922,14 @@ UUID ObjectFilePECOFF::GetUUID() {
870922
return m_uuid;
871923
}
872924

925+
llvm::Optional<FileSpec> ObjectFilePECOFF::GetDebugLink() {
926+
std::string gnu_debuglink_file;
927+
uint32_t gnu_debuglink_crc;
928+
if (GetDebugLinkContents(*m_binary, gnu_debuglink_file, gnu_debuglink_crc))
929+
return FileSpec(gnu_debuglink_file);
930+
return llvm::None;
931+
}
932+
873933
uint32_t ObjectFilePECOFF::ParseDependentModules() {
874934
ModuleSP module_sp(GetModule());
875935
if (!module_sp)

lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ class ObjectFilePECOFF : public lldb_private::ObjectFile {
119119

120120
lldb_private::UUID GetUUID() override;
121121

122+
/// Return the contents of the .gnu_debuglink section, if the object file
123+
/// contains it.
124+
llvm::Optional<lldb_private::FileSpec> GetDebugLink();
125+
122126
uint32_t GetDependentModules(lldb_private::FileSpecList &files) override;
123127

124128
lldb_private::Address GetEntryPointAddress() override;

lldb/source/Plugins/SymbolVendor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
44
add_subdirectory(MacOSX)
55
endif()
66

7+
add_subdirectory(PECOFF)
78
add_subdirectory(wasm)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
add_lldb_library(lldbPluginSymbolVendorPECOFF PLUGIN
2+
SymbolVendorPECOFF.cpp
3+
4+
LINK_LIBS
5+
lldbCore
6+
lldbHost
7+
lldbSymbol
8+
lldbPluginObjectFilePECOFF
9+
)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//===-- SymbolVendorPECOFF.cpp --------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "SymbolVendorPECOFF.h"
10+
11+
#include <cstring>
12+
13+
#include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h"
14+
#include "lldb/Core/Module.h"
15+
#include "lldb/Core/ModuleSpec.h"
16+
#include "lldb/Core/PluginManager.h"
17+
#include "lldb/Core/Section.h"
18+
#include "lldb/Host/Host.h"
19+
#include "lldb/Symbol/LocateSymbolFile.h"
20+
#include "lldb/Symbol/ObjectFile.h"
21+
#include "lldb/Target/Target.h"
22+
#include "lldb/Utility/StreamString.h"
23+
#include "lldb/Utility/Timer.h"
24+
25+
using namespace lldb;
26+
using namespace lldb_private;
27+
28+
LLDB_PLUGIN_DEFINE(SymbolVendorPECOFF)
29+
30+
// SymbolVendorPECOFF constructor
31+
SymbolVendorPECOFF::SymbolVendorPECOFF(const lldb::ModuleSP &module_sp)
32+
: SymbolVendor(module_sp) {}
33+
34+
void SymbolVendorPECOFF::Initialize() {
35+
PluginManager::RegisterPlugin(GetPluginNameStatic(),
36+
GetPluginDescriptionStatic(), CreateInstance);
37+
}
38+
39+
void SymbolVendorPECOFF::Terminate() {
40+
PluginManager::UnregisterPlugin(CreateInstance);
41+
}
42+
43+
llvm::StringRef SymbolVendorPECOFF::GetPluginDescriptionStatic() {
44+
return "Symbol vendor for PE/COFF that looks for dSYM files that match "
45+
"executables.";
46+
}
47+
48+
// CreateInstance
49+
//
50+
// Platforms can register a callback to use when creating symbol vendors to
51+
// allow for complex debug information file setups, and to also allow for
52+
// finding separate debug information files.
53+
SymbolVendor *
54+
SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp,
55+
lldb_private::Stream *feedback_strm) {
56+
if (!module_sp)
57+
return nullptr;
58+
59+
ObjectFilePECOFF *obj_file =
60+
llvm::dyn_cast_or_null<ObjectFilePECOFF>(module_sp->GetObjectFile());
61+
if (!obj_file)
62+
return nullptr;
63+
64+
lldb_private::UUID uuid = obj_file->GetUUID();
65+
if (!uuid)
66+
return nullptr;
67+
68+
// If the main object file already contains debug info, then we are done.
69+
if (obj_file->GetSectionList()->FindSectionByType(
70+
lldb::eSectionTypeDWARFDebugInfo, true))
71+
return nullptr;
72+
73+
// If the module specified a filespec, use that.
74+
FileSpec fspec = module_sp->GetSymbolFileFileSpec();
75+
// Otherwise, try gnu_debuglink, if one exists.
76+
if (!fspec)
77+
fspec = obj_file->GetDebugLink().getValueOr(FileSpec());
78+
79+
LLDB_SCOPED_TIMERF("SymbolVendorPECOFF::CreateInstance (module = %s)",
80+
module_sp->GetFileSpec().GetPath().c_str());
81+
82+
ModuleSpec module_spec;
83+
84+
module_spec.GetFileSpec() = obj_file->GetFileSpec();
85+
FileSystem::Instance().Resolve(module_spec.GetFileSpec());
86+
module_spec.GetSymbolFileSpec() = fspec;
87+
module_spec.GetUUID() = uuid;
88+
FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths();
89+
FileSpec dsym_fspec =
90+
Symbols::LocateExecutableSymbolFile(module_spec, search_paths);
91+
if (!dsym_fspec)
92+
return nullptr;
93+
94+
DataBufferSP dsym_file_data_sp;
95+
lldb::offset_t dsym_file_data_offset = 0;
96+
ObjectFileSP dsym_objfile_sp = ObjectFile::FindPlugin(
97+
module_sp, &dsym_fspec, 0, FileSystem::Instance().GetByteSize(dsym_fspec),
98+
dsym_file_data_sp, dsym_file_data_offset);
99+
if (!dsym_objfile_sp)
100+
return nullptr;
101+
102+
// This objfile is for debugging purposes.
103+
dsym_objfile_sp->SetType(ObjectFile::eTypeDebugInfo);
104+
105+
SymbolVendorPECOFF *symbol_vendor = new SymbolVendorPECOFF(module_sp);
106+
107+
// Get the module unified section list and add our debug sections to
108+
// that.
109+
SectionList *module_section_list = module_sp->GetSectionList();
110+
SectionList *objfile_section_list = dsym_objfile_sp->GetSectionList();
111+
if (!objfile_section_list || !module_section_list)
112+
return nullptr;
113+
114+
static const SectionType g_sections[] = {
115+
eSectionTypeDWARFDebugAbbrev, eSectionTypeDWARFDebugAranges,
116+
eSectionTypeDWARFDebugFrame, eSectionTypeDWARFDebugInfo,
117+
eSectionTypeDWARFDebugLine, eSectionTypeDWARFDebugLoc,
118+
eSectionTypeDWARFDebugLocLists, eSectionTypeDWARFDebugMacInfo,
119+
eSectionTypeDWARFDebugNames, eSectionTypeDWARFDebugPubNames,
120+
eSectionTypeDWARFDebugPubTypes, eSectionTypeDWARFDebugRanges,
121+
eSectionTypeDWARFDebugStr, eSectionTypeDWARFDebugTypes,
122+
};
123+
for (SectionType section_type : g_sections) {
124+
if (SectionSP section_sp =
125+
objfile_section_list->FindSectionByType(section_type, true)) {
126+
if (SectionSP module_section_sp =
127+
module_section_list->FindSectionByType(section_type, true))
128+
module_section_list->ReplaceSection(module_section_sp->GetID(),
129+
section_sp);
130+
else
131+
module_section_list->AddSection(section_sp);
132+
}
133+
}
134+
135+
symbol_vendor->AddSymbolFileRepresentation(dsym_objfile_sp);
136+
return symbol_vendor;
137+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===-- SymbolVendorPECOFF.h ------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_PLUGINS_SYMBOLVENDOR_PECOFF_SYMBOLVENDORPECOFF_H
10+
#define LLDB_SOURCE_PLUGINS_SYMBOLVENDOR_PECOFF_SYMBOLVENDORPECOFF_H
11+
12+
#include "lldb/Symbol/SymbolVendor.h"
13+
#include "lldb/lldb-private.h"
14+
15+
class SymbolVendorPECOFF : public lldb_private::SymbolVendor {
16+
public:
17+
// Constructors and Destructors
18+
SymbolVendorPECOFF(const lldb::ModuleSP &module_sp);
19+
20+
// Static Functions
21+
static void Initialize();
22+
23+
static void Terminate();
24+
25+
static llvm::StringRef GetPluginNameStatic() { return "PE-COFF"; }
26+
27+
static llvm::StringRef GetPluginDescriptionStatic();
28+
29+
static lldb_private::SymbolVendor *
30+
CreateInstance(const lldb::ModuleSP &module_sp,
31+
lldb_private::Stream *feedback_strm);
32+
33+
// PluginInterface protocol
34+
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
35+
};
36+
37+
#endif // LLDB_SOURCE_PLUGINS_SYMBOLVENDOR_PECOFF_SYMBOLVENDORPECOFF_H

lldb/source/Symbol/LocateSymbolFile.cpp

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -362,18 +362,34 @@ Symbols::LocateExecutableSymbolFile(const ModuleSpec &module_spec,
362362
lldb_private::ModuleSpecList specs;
363363
const size_t num_specs =
364364
ObjectFile::GetModuleSpecifications(file_spec, 0, 0, specs);
365-
assert(num_specs <= 1 &&
366-
"Symbol Vendor supports only a single architecture");
367-
if (num_specs == 1) {
368-
ModuleSpec mspec;
369-
if (specs.GetModuleSpecAtIndex(0, mspec)) {
370-
// Skip the uuids check if module_uuid is invalid. For example,
371-
// this happens for *.dwp files since at the moment llvm-dwp
372-
// doesn't output build ids, nor does binutils dwp.
373-
if (!module_uuid.IsValid() || module_uuid == mspec.GetUUID())
374-
return file_spec;
365+
ModuleSpec mspec;
366+
bool valid_mspec = false;
367+
if (num_specs == 2) {
368+
// Special case to handle both i386 and i686 from ObjectFilePECOFF
369+
ModuleSpec mspec2;
370+
if (specs.GetModuleSpecAtIndex(0, mspec) &&
371+
specs.GetModuleSpecAtIndex(1, mspec2) &&
372+
mspec.GetArchitecture().GetTriple().isCompatibleWith(
373+
mspec2.GetArchitecture().GetTriple())) {
374+
valid_mspec = true;
375375
}
376376
}
377+
if (!valid_mspec) {
378+
assert(num_specs <= 1 &&
379+
"Symbol Vendor supports only a single architecture");
380+
if (num_specs == 1) {
381+
if (specs.GetModuleSpecAtIndex(0, mspec)) {
382+
valid_mspec = true;
383+
}
384+
}
385+
}
386+
if (valid_mspec) {
387+
// Skip the uuids check if module_uuid is invalid. For example,
388+
// this happens for *.dwp files since at the moment llvm-dwp
389+
// doesn't output build ids, nor does binutils dwp.
390+
if (!module_uuid.IsValid() || module_uuid == mspec.GetUUID())
391+
return file_spec;
392+
}
377393
}
378394
}
379395
}

lldb/test/Shell/Minidump/Windows/Inputs/find-module.exe.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ OptionalHeader:
1616
SizeOfStackCommit: 4096
1717
SizeOfHeapReserve: 1048576
1818
SizeOfHeapCommit: 4096
19+
Debug:
20+
RelativeVirtualAddress: 20480
21+
Size: 28
1922
header:
2023
Machine: IMAGE_FILE_MACHINE_I386
2124
Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_32BIT_MACHINE ]
@@ -28,5 +31,10 @@ sections:
2831
Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ ]
2932
VirtualAddress: 16384
3033
VirtualSize: 48
34+
- Name: .buildid
35+
Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
36+
VirtualAddress: 20480
37+
VirtualSize: 194
38+
SectionData: 00000000E5038E620000000002000000A60000001C5000001C400000525344533ED87D89C8A8184197F3A925EE4BF74101000000433A5C70726F6A656374735C746573745F6170705C436F6E736F6C654170706C69636174696F6E315C44656275675C436F6E736F6C654170706C69636174696F6E312E70646200
3139
symbols: []
3240
...

0 commit comments

Comments
 (0)