Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to map PT blocks to LLVM IR blocks. #336

Merged
merged 1 commit into from May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -5,4 +5,5 @@ members = [
"ykllvmwrap",
"ykrt",
"yktrace",
"ykutil",
]
2 changes: 1 addition & 1 deletion bors.toml
@@ -1,5 +1,5 @@
# The service providing the commit statuses to GitHub.
status = ["buildbot/yorick-buildbot-build-script-hw"]
status = ["buildbot/yk-buildbot-build-script-hw"]

# Allow eight hours for builds + tests.
timeout_sec = 28800
Expand Down
51 changes: 51 additions & 0 deletions c_tests/tests/mapper_funcs_branches.c
@@ -0,0 +1,51 @@
// Compiler:
ptersilie marked this conversation as resolved.
Show resolved Hide resolved
// Run-time:

// Check that inter-procedural tracing works.

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yk_testing.h>

__attribute__((noinline))
int
add_one_or_two(int arg)
{
if (arg % 2 == 0) {
arg++;
} else {
arg += 2;
}
return arg;
}

int
main(int argc, char **argv)
{
void *tt = __yktrace_start_tracing(HW_TRACING);
argc = add_one_or_two(argc);
void *tr = __yktrace_stop_tracing(tt);

size_t len = __yktrace_irtrace_len(tr);
assert(len >= 4); // At least one block for `main`, at least 3 blocks for `add_one_or_two`.
for (int i = 0; i < len; i++) {
char *func_name = NULL;
size_t bb = 0;
__yktrace_irtrace_get(tr, i, &func_name, &bb);

// We expect blocks from `add_one_or_two` to be book-ended by `main` bb0.
// Note that in LLVM, a call does not terminate a block.
if ((i == 0) || (i == len - 1)) {
assert((strcmp(func_name, "main") == 0) && (bb == 0));
} else {
assert(strcmp(func_name, "add_one_or_two") == 0);
}
free(func_name);
}

__yktrace_drop_irtrace(tr);
return (EXIT_SUCCESS);
}
29 changes: 29 additions & 0 deletions c_tests/tests/mapper_simple.c
@@ -0,0 +1,29 @@
// Compiler:
// Run-time:

// Check that basic tracing works.

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yk_testing.h>

int
main(int argc, char **argv)
{
void *tt = __yktrace_start_tracing(HW_TRACING);
void *tr = __yktrace_stop_tracing(tt);
assert(__yktrace_irtrace_len(tr) == 1);

char *func_name = NULL;
size_t bb = 0;
__yktrace_irtrace_get(tr, 0, &func_name, &bb);
assert(strcmp(func_name, "main") == 0);
assert(bb == 0);

free(func_name);
__yktrace_drop_irtrace(tr);

return (EXIT_SUCCESS);
}
56 changes: 54 additions & 2 deletions ykcapi/src/lib.rs
Expand Up @@ -42,7 +42,11 @@ pub extern "C" fn yk_drop_location(loc: *mut Location) {
/// These symbols are not shipped as part of the main API.
#[cfg(feature = "c_testing")]
mod c_testing {
use yktrace::BlockMap;
use std::{ffi::CString, mem, os::raw::c_char};
use yktrace::{start_tracing, BlockMap, IRTrace, ThreadTracer, TracingKind};

const SW_TRACING: usize = 0;
const HW_TRACING: usize = 1;

#[no_mangle]
pub extern "C" fn __yktrace_hwt_mapper_blockmap_new() -> *mut BlockMap {
Expand All @@ -58,7 +62,55 @@ mod c_testing {
pub extern "C" fn __yktrace_hwt_mapper_blockmap_len(bm: *mut BlockMap) -> usize {
let bm = unsafe { Box::from_raw(bm) };
let ret = bm.len();
Box::leak(bm);
mem::forget(bm);
ret
}

#[no_mangle]
pub extern "C" fn __yktrace_start_tracing(kind: usize) -> *mut ThreadTracer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We presumably need to document somewhere that this returns a Boxed thing? Even if it's just a comment that says "if you don't follow this up with __yktrace_stop_tracing you're in undefined behaviour"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do yeah, but I've not been too worried about overly documenting code in the testing interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good point. OK I'm less worried if it's purely used for testing.

let kind: TracingKind = match kind {
SW_TRACING => TracingKind::SoftwareTracing,
HW_TRACING => TracingKind::HardwareTracing,
_ => panic!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this turning a compile-time error into a run-time error? If you remove the _=>panic!() line, the compiler will refuse to compile this if we add a new TracingKind and don't expand the match accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. The match has to be exhaustive, yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My proposal is that you remove the line _ => panic!().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work. The match is over an integer (due to C ABI reasons).

72 |         let kind: TracingKind = match kind {
   |                                       ^^^^ pattern `_` not covered

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha!

};
Box::into_raw(Box::new(start_tracing(kind)))
}

#[no_mangle]
pub extern "C" fn __yktrace_stop_tracing(tt: *mut ThreadTracer) -> *mut IRTrace {
vext01 marked this conversation as resolved.
Show resolved Hide resolved
let tt = unsafe { Box::from_raw(tt) };
Box::into_raw(Box::new(tt.stop_tracing().unwrap())) as *mut _
}

#[no_mangle]
pub extern "C" fn __yktrace_irtrace_len(trace: *mut IRTrace) -> usize {
let trace = unsafe { Box::from_raw(trace) };
let ret = trace.len();
mem::forget(trace);
ret
}

/// Fetches the function name (`res_func`) and the block index (`res_bb`) at position `idx` in
/// `trace`. The caller must free() the function name.
#[no_mangle]
pub extern "C" fn __yktrace_irtrace_get(
trace: *mut IRTrace,
idx: usize,
res_func: *mut *mut c_char,
res_bb: *mut usize,
) {
let trace = unsafe { Box::from_raw(trace) };
vext01 marked this conversation as resolved.
Show resolved Hide resolved
let blk = trace.get(idx).unwrap();
let c_func = CString::new(blk.func_name()).unwrap();
unsafe {
*res_func = c_func.into_raw();
*res_bb = blk.bb();
}
mem::forget(trace);
}

#[no_mangle]
pub extern "C" fn __yktrace_drop_irtrace(trace: *mut IRTrace) {
unsafe { Box::from_raw(trace) };
}
}
12 changes: 12 additions & 0 deletions ykcapi/yk_testing.h
@@ -1,5 +1,17 @@
// Functions exported only for testing.

#include <stdint.h>

#define SW_TRACING 0
#define HW_TRACING 1

void *__yktrace_hwt_mapper_blockmap_new(void);
size_t __yktrace_hwt_mapper_blockmap_len(void *mapper);
void __yktrace_hwt_mapper_blockmap_free(void *mapper);

void *__yktrace_start_tracing(uintptr_t kind);
void *__yktrace_stop_tracing(void *tt);

size_t __yktrace_irtrace_len(void *trace);
void __yktrace_irtrace_get(void *trace, size_t idx, char **res_func, size_t *res_bb);
void __yktrace_drop_irtrace(void *trace);
1 change: 1 addition & 0 deletions ykllvmwrap/Cargo.toml
Expand Up @@ -8,6 +8,7 @@ license = "Apache-2.0 OR MIT"
[dependencies]
cc = "1.0.67"
libc = "0.2.94"
ykutil = { path = "../ykutil" }

[build-dependencies]
cc = "1.0.67"
41 changes: 24 additions & 17 deletions ykllvmwrap/src/symbolizer.rs
Expand Up @@ -10,7 +10,11 @@ use std::{
extern "C" {
fn __yk_symbolizer_new() -> *mut c_void;
fn __yk_symbolizer_free(symbolizer: *mut c_void);
fn __yk_symbolizer_find_code_sym(symbolizer: *mut c_void, addr: usize) -> *mut c_char;
fn __yk_symbolizer_find_code_sym(
symbolizer: *mut c_void,
obj: *const c_char,
off: u64,
) -> *mut c_char;
}

pub struct Symbolizer(*mut c_void);
Expand All @@ -20,15 +24,20 @@ impl Symbolizer {
Self(unsafe { __yk_symbolizer_new() })
}

/// Returns the name of the symbol at the provided virtual address.
pub fn find_code_sym(&self, vaddr: usize) -> Option<String> {
let ptr = unsafe { __yk_symbolizer_find_code_sym(self.0, vaddr) };
/// Returns the name of the symbol at byte offset `off` in the object file `obj`,
/// or `None` if the symbol couldn't be found.
pub fn find_code_sym(&self, obj: &CStr, off: u64) -> Option<String> {
let ptr = unsafe { __yk_symbolizer_find_code_sym(self.0, obj.as_ptr(), off) };
if ptr.is_null() {
None
} else {
let ret = {
let sym = unsafe { CStr::from_ptr(ptr) };
String::from(sym.to_str().unwrap())
let sym = String::from(sym.to_str().unwrap());
if sym == "<invalid>" {
return None;
}
sym
};
unsafe { free(ptr as *mut _) };
Some(ret)
Expand All @@ -45,6 +54,7 @@ impl Drop for Symbolizer {
#[cfg(test)]
mod tests {
use super::Symbolizer;
use ykutil::addr::code_vaddr_to_off;

extern "C" {
fn getuid();
Expand All @@ -63,9 +73,10 @@ mod tests {

#[test]
fn find_code_sym_mangled() {
let f_addr = symbolize_me_mangled as *const fn() as *const _ as usize;
let f_vaddr = symbolize_me_mangled as *const fn() as *const _ as usize;
let (obj, f_off) = code_vaddr_to_off(f_vaddr).unwrap();
let s = Symbolizer::new();
let sym = s.find_code_sym(f_addr).unwrap();
let sym = s.find_code_sym(obj, f_off).unwrap();
// The symbol will be suffixed with an auto-generated module name, e.g.:
// ykllvmwrap::symbolizer::tests::symbolize_me_mangled::hc7a76ddceae6f9c4
assert!(sym.starts_with("ykllvmwrap::symbolizer::tests::symbolize_me_mangled::"));
Expand All @@ -75,23 +86,19 @@ mod tests {

#[test]
fn find_code_sym_unmangled() {
let f_addr = symbolize_me_unmangled as *const fn() as *const _ as usize;
let f_vaddr = symbolize_me_unmangled as *const fn() as *const _ as usize;
let (obj, f_off) = code_vaddr_to_off(f_vaddr).unwrap();
let s = Symbolizer::new();
let sym = s.find_code_sym(f_addr).unwrap();
let sym = s.find_code_sym(obj, f_off).unwrap();
assert_eq!(sym, "symbolize_me_unmangled");
}

#[test]
fn find_code_sym_libc() {
let f_addr = getuid as *const fn() as *const _ as usize;
let f_vaddr = getuid as *const fn() as *const _ as usize;
let (obj, f_off) = code_vaddr_to_off(f_vaddr).unwrap();
let s = Symbolizer::new();
let sym = s.find_code_sym(f_addr).unwrap();
let sym = s.find_code_sym(obj, f_off).unwrap();
assert_eq!(sym, "getuid");
}

#[test]
fn find_code_sym_not_found() {
// Virtual address 0x1 is obviously nonsense.
assert!(Symbolizer::new().find_code_sym(1).is_none());
}
}
22 changes: 5 additions & 17 deletions ykllvmwrap/src/ykllvmwrap.cc
Expand Up @@ -7,6 +7,7 @@
#include <dlfcn.h>
#include <llvm/DebugInfo/Symbolize/Symbolize.h>
#include <string.h>
#include <link.h>

using namespace llvm;
using namespace llvm::symbolize;
Expand All @@ -21,23 +22,10 @@ extern "C" void __yk_symbolizer_free(LLVMSymbolizer *Symbolizer) {

// Finds the name of a code symbol from a virtual address.
// The caller is responsible for freeing the returned (heap-allocated) C string.
extern "C" char *__yk_symbolizer_find_code_sym(LLVMSymbolizer *Symbolizer, void *Vaddr) {
// Find which of the loaded objects holds this virtual address.
Dl_info Info;
if (dladdr(Vaddr, &Info) == 0 ) {
return NULL;
}

// Find the corresponding offset of vaddr from the start of the object.
auto Offs = (uintptr_t) Vaddr - (uintptr_t) Info.dli_fbase;

// Use the LLVM symbolizer to find the symbol name. Note that we don't
// simply rely on `Info.dli_sname`, as `dl_addr()` can only find a subset
// of symbols -- those exported -- and we don't want users to have to link
// with --export-dynamic.
auto LineInfo = Symbolizer->symbolizeCode(Info.dli_fname,
{Offs, object::SectionedAddress::UndefSection});
if (auto err = LineInfo.takeError()) {
extern "C" char *__yk_symbolizer_find_code_sym(LLVMSymbolizer *Symbolizer, const char *Obj, uint64_t Off) {
object::SectionedAddress Mod{Off, object::SectionedAddress::UndefSection};
auto LineInfo = Symbolizer->symbolizeCode(Obj, Mod);
if (auto Err = LineInfo.takeError()) {
return NULL;
}

Expand Down
5 changes: 5 additions & 0 deletions yktrace/Cargo.toml
Expand Up @@ -7,13 +7,18 @@ license = "Apache-2.0 OR MIT"

[dependencies]
byteorder = "1.4.3"
elf = "0.0.10"
fxhash = "0.2.1"
gimli = "0.23.0"
hwtracer = { git = "https://github.com/softdevteam/hwtracer" }
intervaltree = "0.2.6"
leb128 = "0.2.4"
libc = "0.2.82"
memmap2 = "0.2.2"
once_cell = "1.7.2"
phdrs = { git = "https://github.com/softdevteam/phdrs" }
ykllvmwrap = { path = "../ykllvmwrap" }
ykutil = { path = "../ykutil" }

[dependencies.object]
version = "0.23.0"
Expand Down