diff --git a/.github/actions/define-llvm-env/action.yml b/.github/actions/define-llvm-env/action.yml index 36f77b60b8..168054a04f 100644 --- a/.github/actions/define-llvm-env/action.yml +++ b/.github/actions/define-llvm-env/action.yml @@ -1,5 +1,5 @@ name: 'Set up a DWARFDUMP env' -description: 'Set up a DWARFDUMP env (see tests/debug/dump.rs)' +description: 'Set up a DWARFDUMP env (see tests/all/debug/dump.rs)' runs: using: node12 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9eb4c48186..28f18dffd2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -225,7 +225,9 @@ jobs: RUST_BACKTRACE: 1 # Test debug (DWARF) related functionality. - - run: cargo test test_debug_dwarf -- --ignored --test-threads 1 + - run: | + sudo apt-get install -y gdb + cargo test test_debug_dwarf -- --ignored --test-threads 1 if: matrix.os == 'ubuntu-latest' env: RUST_BACKTRACE: 1 diff --git a/crates/debug/src/lib.rs b/crates/debug/src/lib.rs index 45edf58904..c72228c83f 100644 --- a/crates/debug/src/lib.rs +++ b/crates/debug/src/lib.rs @@ -117,43 +117,53 @@ pub fn write_debugsections_image( } fn convert_object_elf_to_loadable_file(bytes: &mut Vec, code_ptr: *const u8) { + use object::elf::*; + use object::endian::LittleEndian; use std::ffi::CStr; + use std::mem::size_of; use std::os::raw::c_char; + let e = LittleEndian; + let header: &FileHeader64 = + unsafe { &*(bytes.as_mut_ptr() as *const FileHeader64<_>) }; assert!( - bytes[0x4] == 2 && bytes[0x5] == 1, - "bits and endianess in .ELF" + header.e_ident.class == ELFCLASS64 && header.e_ident.data == ELFDATA2LSB, + "bits and endianess in .ELF", ); - let e_phoff = unsafe { *(bytes.as_ptr().offset(0x20) as *const u64) }; - let e_phnum = unsafe { *(bytes.as_ptr().offset(0x38) as *const u16) }; assert!( - e_phoff == 0 && e_phnum == 0, + header.e_phoff.get(e) == 0 && header.e_phnum.get(e) == 0, "program header table is empty" ); - let e_shentsize = unsafe { *(bytes.as_ptr().offset(0x3A) as *const u16) }; - assert_eq!(e_shentsize, 0x40, "size of sh"); + let e_shentsize = header.e_shentsize.get(e); + assert_eq!( + e_shentsize as usize, + size_of::>(), + "size of sh" + ); - let e_shoff = unsafe { *(bytes.as_ptr().offset(0x28) as *const u64) }; - let e_shnum = unsafe { *(bytes.as_ptr().offset(0x3C) as *const u16) }; + let e_shoff = header.e_shoff.get(e); + let e_shnum = header.e_shnum.get(e); let mut shstrtab_off = 0; for i in 0..e_shnum { let off = e_shoff as isize + i as isize * e_shentsize as isize; - let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) }; - if sh_type != /* SHT_SYMTAB */ 3 { + let section: &SectionHeader64 = + unsafe { &*(bytes.as_ptr().offset(off) as *const SectionHeader64<_>) }; + if section.sh_type.get(e) != SHT_STRTAB { continue; } - shstrtab_off = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) }; + shstrtab_off = section.sh_offset.get(e); } let mut segment = None; for i in 0..e_shnum { let off = e_shoff as isize + i as isize * e_shentsize as isize; - let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) }; - if sh_type != /* SHT_PROGBITS */ 1 { + let section: &mut SectionHeader64 = + unsafe { &mut *(bytes.as_mut_ptr().offset(off) as *mut SectionHeader64<_>) }; + if section.sh_type.get(e) != SHT_PROGBITS { continue; } // It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function + let sh_name_off = section.sh_name.get(e); let sh_name = unsafe { - let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32); CStr::from_ptr( bytes .as_ptr() @@ -170,42 +180,36 @@ fn convert_object_elf_to_loadable_file(bytes: &mut Vec, code_ptr: *const u8) assert!(segment.is_none()); // Functions was added at write_debugsections_image as .text.all. // Patch vaddr, and save file location and its size. - unsafe { - *(bytes.as_ptr().offset(off + 0x10) as *mut u64) = code_ptr as u64; - }; - let sh_offset = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) }; - let sh_size = unsafe { *(bytes.as_ptr().offset(off + 0x20) as *const u64) }; + section.sh_addr.set(e, code_ptr as u64); + let sh_offset = section.sh_offset.get(e); + let sh_size = section.sh_size.get(e); segment = Some((sh_offset, code_ptr, sh_size)); // Fix name too: cut it to just ".text" - unsafe { - let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32); - bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0; - } + bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0; } // LLDB wants segment with virtual address set, placing them at the end of ELF. let ph_off = bytes.len(); + let e_phentsize = size_of::>(); if let Some((sh_offset, v_offset, sh_size)) = segment { - let segment = vec![0; 0x38]; - unsafe { - *(segment.as_ptr() as *mut u32) = /* PT_LOAD */ 0x1; - *(segment.as_ptr().offset(0x8) as *mut u64) = sh_offset; - *(segment.as_ptr().offset(0x10) as *mut u64) = v_offset as u64; - *(segment.as_ptr().offset(0x18) as *mut u64) = v_offset as u64; - *(segment.as_ptr().offset(0x20) as *mut u64) = sh_size; - *(segment.as_ptr().offset(0x28) as *mut u64) = sh_size; - } - bytes.extend_from_slice(&segment); + bytes.resize(ph_off + e_phentsize, 0); + let program: &mut ProgramHeader64 = + unsafe { &mut *(bytes.as_ptr().add(ph_off) as *mut ProgramHeader64<_>) }; + program.p_type.set(e, PT_LOAD); + program.p_offset.set(e, sh_offset); + program.p_vaddr.set(e, v_offset as u64); + program.p_paddr.set(e, v_offset as u64); + program.p_filesz.set(e, sh_size as u64); + program.p_memsz.set(e, sh_size as u64); } else { unreachable!(); } // It is somewhat loadable ELF file at this moment. - // Update e_flags, e_phoff, e_phentsize and e_phnum. - unsafe { - *(bytes.as_ptr().offset(0x10) as *mut u16) = /* ET_DYN */ 3; - *(bytes.as_ptr().offset(0x20) as *mut u64) = ph_off as u64; - *(bytes.as_ptr().offset(0x36) as *mut u16) = 0x38 as u16; - *(bytes.as_ptr().offset(0x38) as *mut u16) = 1 as u16; - } + let header: &mut FileHeader64 = + unsafe { &mut *(bytes.as_mut_ptr() as *mut FileHeader64<_>) }; + header.e_type.set(e, ET_DYN); + header.e_phoff.set(e, ph_off as u64); + header.e_phentsize.set(e, e_phentsize as u16); + header.e_phnum.set(e, 1u16); } diff --git a/tests/all/debug/gdb.rs b/tests/all/debug/gdb.rs new file mode 100644 index 0000000000..c67edddf8e --- /dev/null +++ b/tests/all/debug/gdb.rs @@ -0,0 +1,85 @@ +#![allow(dead_code)] + +use anyhow::{bail, format_err, Result}; +use filecheck::{CheckerBuilder, NO_VARIABLES}; +use std::env; +use std::io::Write; +use std::process::Command; +use tempfile::NamedTempFile; + +fn gdb_with_script(args: &[&str], script: &str) -> Result { + let lldb_path = env::var("GDB").unwrap_or("gdb".to_string()); + let mut cmd = Command::new(&lldb_path); + + cmd.arg("--batch"); + let mut script_file = NamedTempFile::new()?; + script_file.write(script.as_bytes())?; + let script_path = script_file.path().to_str().unwrap(); + cmd.args(&["-x", &script_path]); + + cmd.arg("--args"); + + let mut me = std::env::current_exe().expect("current_exe specified"); + me.pop(); // chop off the file name + me.pop(); // chop off `deps` + me.push("wasmtime"); + cmd.arg(me); + + cmd.args(args); + + let output = cmd.output().expect("success"); + if !output.status.success() { + bail!( + "failed to execute {:?}: {}", + cmd, + String::from_utf8_lossy(&output.stderr), + ); + } + Ok(String::from_utf8(output.stdout)?) +} + +fn check_gdb_output(output: &str, directives: &str) -> Result<()> { + let mut builder = CheckerBuilder::new(); + builder + .text(directives) + .map_err(|e| format_err!("unable to build checker: {:?}", e))?; + let checker = builder.finish(); + let check = checker + .explain(output, NO_VARIABLES) + .map_err(|e| format_err!("{:?}", e))?; + assert!(check.0, "didn't pass check {}", check.1); + Ok(()) +} + +#[test] +#[ignore] +#[cfg(all(target_os = "linux", target_pointer_width = "64"))] +pub fn test_debug_dwarf_gdb() -> Result<()> { + let output = gdb_with_script( + &[ + "-g", + "tests/all/debug/testsuite/fib-wasm.wasm", + "--invoke", + "fib", + "3", + ], + r#"set breakpoint pending on +b fib +r +info locals +c"#, + )?; + + check_gdb_output( + &output, + r#" +check: Breakpoint 1 (fib) pending +check: hit Breakpoint 1 +sameln: fib (n=3) +check: a = 0 +check: b = 0 +check: exited normally +"#, + )?; + Ok(()) +} diff --git a/tests/all/debug/mod.rs b/tests/all/debug/mod.rs index 6b36ba855d..ece60b93af 100644 --- a/tests/all/debug/mod.rs +++ b/tests/all/debug/mod.rs @@ -1,4 +1,5 @@ mod dump; +mod gdb; mod lldb; mod obj; mod simulate; diff --git a/tests/all/debug/translate.rs b/tests/all/debug/translate.rs index 1fba1c35c4..390067c17e 100644 --- a/tests/all/debug/translate.rs +++ b/tests/all/debug/translate.rs @@ -65,7 +65,7 @@ check: DW_AT_decl_line (10) ))] fn test_debug_dwarf5_translate() -> Result<()> { check_wasm( - "tests/debug/testsuite/fib-wasm-dwarf5.wasm", + "tests/all/debug/testsuite/fib-wasm-dwarf5.wasm", r##" check: DW_TAG_compile_unit # We have "fib" function diff --git a/tests/debug/testsuite/fib-wasm-dwarf5.wasm b/tests/debug/testsuite/fib-wasm-dwarf5.wasm deleted file mode 100755 index 4517916cb1..0000000000 Binary files a/tests/debug/testsuite/fib-wasm-dwarf5.wasm and /dev/null differ