Skip to content

Commit 7bab808

Browse files
committed
[asan] Print VAs instead of RVAs for module offsets on Windows
Summary: This is consistent with binutils and ASan behavior on other platforms, and makes it easier to use llvm-symbolizer with WinASan. The --relative-address flag to llvm-symbolizer is also no longer needed. An RVA is a "relative virtual address", meaning it is the address of something inside the image minus the base of the mapping at runtime. A VA in this context is an RVA plus the "preferred base" of the module, and not a real runtime address. The real runtime address of a symbol will equal the VA iff the module is loaded at its preferred base at runtime. On Windows, the preferred base is stored in the ImageBase field of one of the PE file header, and this change adds the necessary code to extract it. On Linux, this offset is typically included in program and section headers of executables. ELF shared objects typically use a preferred base of zero, meaning the smallest p_vaddr field in the program headers is zero. This makes it so that PIC and PIE module offsets come out looking like RVAs, but they're actually VAs. The difference between them simply happens to be zero. Reviewers: samsonov, majnemer Subscribers: llvm-commits Differential Revision: http://reviews.llvm.org/D11681 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@243895 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 8eee754 commit 7bab808

File tree

4 files changed

+123
-12
lines changed

4 files changed

+123
-12
lines changed

lib/sanitizer_common/sanitizer_common.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ bool WriteToFile(fd_t fd, const void *buff, uptr buff_size,
224224
bool RenameFile(const char *oldpath, const char *newpath,
225225
error_t *error_p = nullptr);
226226

227+
// Scoped file handle closer.
228+
struct FileCloser {
229+
explicit FileCloser(fd_t fd) : fd(fd) {}
230+
~FileCloser() { CloseFile(fd); }
231+
fd_t fd;
232+
};
233+
227234
bool SupportsColoredOutput(fd_t fd);
228235

229236
// Opens the file 'file_name" and reads up to 'max_len' bytes.

lib/sanitizer_common/sanitizer_win.cc

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,58 @@ void Abort() {
323323
internal__exit(3);
324324
}
325325

326+
// Read the file to extract the ImageBase field from the PE header. If ASLR is
327+
// disabled and this virtual address is available, the loader will typically
328+
// load the image at this address. Therefore, we call it the preferred base. Any
329+
// addresses in the DWARF typically assume that the object has been loaded at
330+
// this address.
331+
static uptr GetPreferredBase(const char *modname) {
332+
fd_t fd = OpenFile(modname, RdOnly, nullptr);
333+
if (fd == kInvalidFd)
334+
return 0;
335+
FileCloser closer(fd);
336+
337+
// Read just the DOS header.
338+
IMAGE_DOS_HEADER dos_header;
339+
uptr bytes_read;
340+
if (!ReadFromFile(fd, &dos_header, sizeof(dos_header), &bytes_read) ||
341+
bytes_read != sizeof(dos_header))
342+
return 0;
343+
344+
// The file should start with the right signature.
345+
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE)
346+
return 0;
347+
348+
// The layout at e_lfanew is:
349+
// "PE\0\0"
350+
// IMAGE_FILE_HEADER
351+
// IMAGE_OPTIONAL_HEADER
352+
// Seek to e_lfanew and read all that data.
353+
char buf[4 + sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_OPTIONAL_HEADER)];
354+
if (::SetFilePointer(fd, dos_header.e_lfanew, nullptr, FILE_BEGIN) ==
355+
INVALID_SET_FILE_POINTER)
356+
return 0;
357+
if (!ReadFromFile(fd, &buf[0], sizeof(buf), &bytes_read) ||
358+
bytes_read != sizeof(buf))
359+
return 0;
360+
361+
// Check for "PE\0\0" before the PE header.
362+
char *pe_sig = &buf[0];
363+
if (internal_memcmp(pe_sig, "PE\0\0", 4) != 0)
364+
return 0;
365+
366+
// Skip over IMAGE_FILE_HEADER. We could do more validation here if we wanted.
367+
IMAGE_OPTIONAL_HEADER *pe_header =
368+
(IMAGE_OPTIONAL_HEADER *)(pe_sig + 4 + sizeof(IMAGE_FILE_HEADER));
369+
370+
// Check for more magic in the PE header.
371+
if (pe_header->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
372+
return 0;
373+
374+
// Finally, return the ImageBase.
375+
return (uptr)pe_header->ImageBase;
376+
}
377+
326378
uptr GetListOfModules(LoadedModule *modules, uptr max_modules,
327379
string_predicate_t filter) {
328380
HANDLE cur_process = GetCurrentProcess();
@@ -355,19 +407,33 @@ uptr GetListOfModules(LoadedModule *modules, uptr max_modules,
355407
if (!GetModuleInformation(cur_process, handle, &mi, sizeof(mi)))
356408
continue;
357409

358-
char module_name[MAX_PATH];
359-
bool got_module_name =
360-
GetModuleFileNameA(handle, module_name, sizeof(module_name));
361-
if (!got_module_name)
362-
module_name[0] = '\0';
410+
// Get the UTF-16 path and convert to UTF-8.
411+
wchar_t modname_utf16[kMaxPathLength];
412+
int modname_utf16_len =
413+
GetModuleFileNameW(handle, modname_utf16, kMaxPathLength);
414+
if (modname_utf16_len == 0)
415+
modname_utf16[0] = '\0';
416+
char module_name[kMaxPathLength];
417+
int module_name_len =
418+
::WideCharToMultiByte(CP_UTF8, 0, modname_utf16, modname_utf16_len + 1,
419+
&module_name[0], kMaxPathLength, NULL, NULL);
420+
module_name[module_name_len] = '\0';
363421

364422
if (filter && !filter(module_name))
365423
continue;
366424

367425
uptr base_address = (uptr)mi.lpBaseOfDll;
368426
uptr end_address = (uptr)mi.lpBaseOfDll + mi.SizeOfImage;
427+
428+
// Adjust the base address of the module so that we get a VA instead of an
429+
// RVA when computing the module offset. This helps llvm-symbolizer find the
430+
// right DWARF CU. In the common case that the image is loaded at it's
431+
// preferred address, we will now print normal virtual addresses.
432+
uptr preferred_base = GetPreferredBase(&module_name[0]);
433+
uptr adjusted_base = base_address - preferred_base;
434+
369435
LoadedModule *cur_module = &modules[count];
370-
cur_module->set(module_name, base_address);
436+
cur_module->set(module_name, adjusted_base);
371437
// We add the whole module as one single address range.
372438
cur_module->addAddressRange(base_address, end_address, /*executable*/ true);
373439
count++;
@@ -402,10 +468,17 @@ static __declspec(allocate(".CRT$XID")) int (*__run_atexit)() = RunAtexit;
402468

403469
// ------------------ sanitizer_libc.h
404470
fd_t OpenFile(const char *filename, FileAccessMode mode, error_t *last_error) {
405-
if (mode != WrOnly)
471+
fd_t res;
472+
if (mode == RdOnly) {
473+
res = CreateFile(filename, GENERIC_READ,
474+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
475+
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
476+
} else if (mode == WrOnly) {
477+
res = CreateFile(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
478+
FILE_ATTRIBUTE_NORMAL, nullptr);
479+
} else {
406480
UNIMPLEMENTED();
407-
fd_t res = CreateFile(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
408-
FILE_ATTRIBUTE_NORMAL, nullptr);
481+
}
409482
CHECK(res != kStdoutFd || kStdoutFd == kInvalidFd);
410483
CHECK(res != kStderrFd || kStderrFd == kInvalidFd);
411484
if (res == kInvalidFd && last_error)
@@ -419,7 +492,11 @@ void CloseFile(fd_t fd) {
419492

420493
bool ReadFromFile(fd_t fd, void *buff, uptr buff_size, uptr *bytes_read,
421494
error_t *error_p) {
422-
UNIMPLEMENTED();
495+
CHECK(fd != kInvalidFd);
496+
bool success = ::ReadFile(fd, buff, buff_size, bytes_read, nullptr);
497+
if (!success && error_p)
498+
*error_p = GetLastError();
499+
return success;
423500
}
424501

425502
bool SupportsColoredOutput(fd_t fd) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// When we link a binary without the -debug flag, ASan should print out VAs
2+
// instead of RVAs. The frames for main and do_uaf should be above 0x400000,
3+
// which is the default image base of an executable.
4+
5+
// RUN: rm -f %t.pdb
6+
// RUN: %clangxx_asan -c -O2 %s -o %t.obj
7+
// RUN: link /nologo /OUT:%t.exe %t.obj %asan_lib %asan_cxx_lib
8+
// RUN: not %run %t.exe 2>&1 | FileCheck %s
9+
10+
#include <stdlib.h>
11+
#include <stdio.h>
12+
int __attribute__((noinline)) do_uaf(void);
13+
int main() {
14+
int r = do_uaf();
15+
printf("r: %d\n", r);
16+
return r;
17+
}
18+
int do_uaf(void) {
19+
char *x = (char*)malloc(10 * sizeof(char));
20+
free(x);
21+
return x[5];
22+
// CHECK: AddressSanitizer: heap-use-after-free
23+
// CHECK: #0 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cc.*.exe\+0x40[a-f0-9]{4}\)}}
24+
// CHECK: #1 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cc.*.exe\+0x40[a-f0-9]{4}\)}}
25+
}

test/asan/lit.cfg

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ if platform.system() == 'Windows':
113113
clang_invocation = build_invocation(clang_cl_asan_cxxflags)
114114
clang_cl_invocation = clang_invocation.replace("clang.exe","clang-cl.exe")
115115
config.substitutions.append( ("%clang_cl_asan ", clang_cl_invocation) )
116-
config.substitutions.append( ("%asan_dll_thunk",
117-
os.path.join(config.compiler_rt_libdir, "clang_rt.asan_dll_thunk-i386.lib")))
116+
base_lib = os.path.join(config.compiler_rt_libdir, "clang_rt.asan%%s-%s.lib" % config.target_arch)
117+
config.substitutions.append( ("%asan_lib", base_lib % "") )
118+
config.substitutions.append( ("%asan_cxx_lib", base_lib % "_cxx") )
119+
config.substitutions.append( ("%asan_dll_thunk", base_lib % "_dll_thunk") )
118120

119121
# FIXME: De-hardcode this path.
120122
asan_source_dir = os.path.join(

0 commit comments

Comments
 (0)