mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge branch 'main' into windows-surgical-dll
Signed-off-by: Folkert de Vries <folkert@folkertdev.nl>
This commit is contained in:
commit
33ae4c2655
111 changed files with 4979 additions and 1882 deletions
|
@ -158,10 +158,68 @@ fn generate_dynamic_lib(
|
|||
// so they must be in alphabetical order
|
||||
custom_names.sort_unstable();
|
||||
|
||||
let bytes = crate::generate_dylib::generate(target, &custom_names)
|
||||
.unwrap_or_else(|e| internal_error!("{}", e));
|
||||
if !dummy_lib_is_up_to_date(target, dummy_lib_path, &custom_names) {
|
||||
let bytes = crate::generate_dylib::generate(target, &custom_names)
|
||||
.unwrap_or_else(|e| internal_error!("{e}"));
|
||||
|
||||
std::fs::write(dummy_lib_path, &bytes).unwrap_or_else(|e| internal_error!("{}", e))
|
||||
std::fs::write(dummy_lib_path, &bytes).unwrap_or_else(|e| internal_error!("{e}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn object_matches_target<'a>(target: &Triple, object: &object::File<'a, &'a [u8]>) -> bool {
|
||||
use target_lexicon::{Architecture as TLA, OperatingSystem as TLO};
|
||||
|
||||
match target.architecture {
|
||||
TLA::X86_64 => {
|
||||
if object.architecture() != object::Architecture::X86_64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let target_format = match target.operating_system {
|
||||
TLO::Linux => object::BinaryFormat::Elf,
|
||||
TLO::Windows => object::BinaryFormat::Pe,
|
||||
_ => todo!("surgical linker does not support target {:?}", target),
|
||||
};
|
||||
|
||||
object.format() == target_format
|
||||
}
|
||||
TLA::Aarch64(_) => object.architecture() == object::Architecture::Aarch64,
|
||||
_ => todo!("surgical linker does not support target {:?}", target),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the dummy `.dll/.so` is up to date, in other words that it exports exactly the
|
||||
/// symbols that it is supposed to export, and is built for the right target. If this is the case,
|
||||
/// we can skip rebuildingthe dummy lib.
|
||||
fn dummy_lib_is_up_to_date(
|
||||
target: &Triple,
|
||||
dummy_lib_path: &Path,
|
||||
custom_names: &[String],
|
||||
) -> bool {
|
||||
if !std::path::Path::exists(dummy_lib_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let exec_file = fs::File::open(dummy_lib_path).unwrap_or_else(|e| internal_error!("{}", e));
|
||||
let exec_mmap = unsafe { Mmap::map(&exec_file).unwrap_or_else(|e| internal_error!("{}", e)) };
|
||||
let exec_data = &*exec_mmap;
|
||||
|
||||
let object = object::File::parse(exec_data).unwrap();
|
||||
|
||||
// the user may have been cross-compiling.
|
||||
// The dynhost on disk must match our current target
|
||||
if !object_matches_target(target, &object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we made this dynhost file. For the file to be the same as what we'd generate,
|
||||
// we need all symbols to be there and in the correct order
|
||||
let dynamic_symbols: Vec<_> = object.exports().unwrap();
|
||||
|
||||
let it1 = dynamic_symbols.iter().map(|e| e.name());
|
||||
let it2 = custom_names.iter().map(|s| s.as_bytes());
|
||||
|
||||
it1.eq(it2)
|
||||
}
|
||||
|
||||
fn is_roc_symbol(sym: &object::Symbol) -> bool {
|
||||
|
@ -224,8 +282,178 @@ fn collect_roc_undefined_symbols<'file, 'data>(
|
|||
.collect()
|
||||
}
|
||||
|
||||
// TODO: Most of this file is a mess of giant functions just to check if things work.
|
||||
// Clean it all up and refactor nicely.
|
||||
struct Surgeries<'a> {
|
||||
surgeries: MutMap<String, Vec<metadata::SurgeryEntry>>,
|
||||
app_func_addresses: MutMap<u64, &'a str>,
|
||||
indirect_warning_given: bool,
|
||||
}
|
||||
|
||||
impl<'a> Surgeries<'a> {
|
||||
fn new(application_symbols: &[Symbol], app_func_addresses: MutMap<u64, &'a str>) -> Self {
|
||||
let mut surgeries = MutMap::default();
|
||||
|
||||
// for each symbol that the host expects from the application
|
||||
// we start with an empty set of places to perform surgery
|
||||
for symbol in application_symbols {
|
||||
let name = symbol.name().unwrap().to_string();
|
||||
surgeries.insert(name, vec![]);
|
||||
}
|
||||
|
||||
Self {
|
||||
surgeries,
|
||||
app_func_addresses,
|
||||
indirect_warning_given: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn append_text_sections(
|
||||
&mut self,
|
||||
object_bytes: &[u8],
|
||||
object: &object::File<'a, &'a [u8]>,
|
||||
verbose: bool,
|
||||
) {
|
||||
let text_sections: Vec<Section> = object
|
||||
.sections()
|
||||
.filter(|sec| sec.kind() == SectionKind::Text)
|
||||
.collect();
|
||||
if text_sections.is_empty() {
|
||||
internal_error!("No text sections found. This application has no code.");
|
||||
}
|
||||
if verbose {
|
||||
println!();
|
||||
println!("Text Sections");
|
||||
for sec in text_sections.iter() {
|
||||
println!("{:+x?}", sec);
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!();
|
||||
println!("Analyzing instuctions for branches");
|
||||
}
|
||||
|
||||
for text_section in text_sections {
|
||||
self.append_text_section(object_bytes, &text_section, verbose)
|
||||
}
|
||||
}
|
||||
|
||||
fn append_text_section(&mut self, object_bytes: &[u8], sec: &Section, verbose: bool) {
|
||||
let (file_offset, compressed) = match sec.compressed_file_range() {
|
||||
Ok(CompressedFileRange {
|
||||
format: CompressionFormat::None,
|
||||
offset,
|
||||
..
|
||||
}) => (offset, false),
|
||||
Ok(range) => (range.offset, true),
|
||||
Err(err) => {
|
||||
internal_error!(
|
||||
"Issues dealing with section compression for {:+x?}: {}",
|
||||
sec,
|
||||
err
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let data = match sec.uncompressed_data() {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
internal_error!("Failed to load text section, {:+x?}: {}", sec, err);
|
||||
}
|
||||
};
|
||||
let mut decoder = Decoder::with_ip(64, &data, sec.address(), DecoderOptions::NONE);
|
||||
let mut inst = Instruction::default();
|
||||
|
||||
while decoder.can_decode() {
|
||||
decoder.decode_out(&mut inst);
|
||||
|
||||
// Note: This gets really complex fast if we want to support more than basic calls/jumps.
|
||||
// A lot of them have to load addresses into registers/memory so we would have to discover that value.
|
||||
// Would probably require some static code analysis and would be impossible in some cases.
|
||||
// As an alternative we can leave in the calls to the plt, but change the plt to jmp to the static function.
|
||||
// That way any indirect call will just have the overhead of an extra jump.
|
||||
match inst.try_op_kind(0) {
|
||||
// Relative Offsets.
|
||||
Ok(OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64) => {
|
||||
let target = inst.near_branch_target();
|
||||
if let Some(func_name) = self.app_func_addresses.get(&target) {
|
||||
if compressed {
|
||||
internal_error!("Surgical linking does not work with compressed text sections: {:+x?}", sec);
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"Found branch from {:+x} to {:+x}({})",
|
||||
inst.ip(),
|
||||
target,
|
||||
func_name
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Double check these offsets are always correct.
|
||||
// We may need to do a custom offset based on opcode instead.
|
||||
let op_kind = inst.op_code().try_op_kind(0).unwrap();
|
||||
let op_size: u8 = match op_kind {
|
||||
OpCodeOperandKind::br16_1 | OpCodeOperandKind::br32_1 => 1,
|
||||
OpCodeOperandKind::br16_2 => 2,
|
||||
OpCodeOperandKind::br32_4 | OpCodeOperandKind::br64_4 => 4,
|
||||
_ => {
|
||||
internal_error!(
|
||||
"Ran into an unknown operand kind when analyzing branches: {:?}",
|
||||
op_kind
|
||||
);
|
||||
}
|
||||
};
|
||||
let offset = inst.next_ip() - op_size as u64 - sec.address() + file_offset;
|
||||
if verbose {
|
||||
println!(
|
||||
"\tNeed to surgically replace {} bytes at file offset {:+x}",
|
||||
op_size, offset,
|
||||
);
|
||||
println!(
|
||||
"\tIts current value is {:+x?}",
|
||||
&object_bytes[offset as usize..(offset + op_size as u64) as usize]
|
||||
)
|
||||
}
|
||||
self.surgeries
|
||||
.get_mut(*func_name)
|
||||
.unwrap()
|
||||
.push(metadata::SurgeryEntry {
|
||||
file_offset: offset,
|
||||
virtual_offset: VirtualOffset::Relative(inst.next_ip()),
|
||||
size: op_size,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(OpKind::FarBranch16 | OpKind::FarBranch32) => {
|
||||
internal_error!(
|
||||
"Found branch type instruction that is not yet support: {:+x?}",
|
||||
inst
|
||||
);
|
||||
}
|
||||
Ok(_) => {
|
||||
if (inst.is_call_far_indirect()
|
||||
|| inst.is_call_near_indirect()
|
||||
|| inst.is_jmp_far_indirect()
|
||||
|| inst.is_jmp_near_indirect())
|
||||
&& !self.indirect_warning_given
|
||||
&& verbose
|
||||
{
|
||||
self.indirect_warning_given = true;
|
||||
println!();
|
||||
println!("Cannot analyze through indirect jmp type instructions");
|
||||
println!("Most likely this is not a problem, but it could mean a loss in optimizations");
|
||||
println!();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
internal_error!("Failed to decode assembly: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a `metadata::Metadata` from a host executable binary, and writes it to disk
|
||||
pub fn preprocess(
|
||||
target: &Triple,
|
||||
exec_filename: &str,
|
||||
|
@ -265,7 +493,9 @@ pub fn preprocess(
|
|||
|
||||
let exec_parsing_duration = exec_parsing_start.elapsed();
|
||||
|
||||
// Extract PLT related information for app functions.
|
||||
// PLT stands for Procedure Linkage Table which is, put simply, used to call external
|
||||
// procedures/functions whose address isn't known in the time of linking, and is left
|
||||
// to be resolved by the dynamic linker at run time.
|
||||
let symbol_and_plt_processing_start = Instant::now();
|
||||
let plt_section_name = match target.binary_format {
|
||||
target_lexicon::BinaryFormat::Elf => ".plt",
|
||||
|
@ -477,7 +707,6 @@ pub fn preprocess(
|
|||
for sym in app_syms.iter() {
|
||||
let name = sym.name().unwrap().to_string();
|
||||
md.app_functions.push(name.clone());
|
||||
md.surgeries.insert(name.clone(), vec![]);
|
||||
md.dynamic_symbol_indices.insert(name, sym.index().0 as u64);
|
||||
}
|
||||
if verbose {
|
||||
|
@ -492,142 +721,13 @@ pub fn preprocess(
|
|||
}
|
||||
let symbol_and_plt_processing_duration = symbol_and_plt_processing_start.elapsed();
|
||||
|
||||
// look at the text (i.e. code) sections and see collect work needs to be done
|
||||
let text_disassembly_start = Instant::now();
|
||||
let text_sections: Vec<Section> = exec_obj
|
||||
.sections()
|
||||
.filter(|sec| sec.kind() == SectionKind::Text)
|
||||
.collect();
|
||||
if text_sections.is_empty() {
|
||||
internal_error!("No text sections found. This application has no code.");
|
||||
}
|
||||
if verbose {
|
||||
println!();
|
||||
println!("Text Sections");
|
||||
for sec in text_sections.iter() {
|
||||
println!("{:+x?}", sec);
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!();
|
||||
println!("Analyzing instuctions for branches");
|
||||
}
|
||||
let mut indirect_warning_given = false;
|
||||
for sec in text_sections {
|
||||
let (file_offset, compressed) = match sec.compressed_file_range() {
|
||||
Ok(
|
||||
range @ CompressedFileRange {
|
||||
format: CompressionFormat::None,
|
||||
..
|
||||
},
|
||||
) => (range.offset, false),
|
||||
Ok(range) => (range.offset, true),
|
||||
Err(err) => {
|
||||
internal_error!(
|
||||
"Issues dealing with section compression for {:+x?}: {}",
|
||||
sec,
|
||||
err
|
||||
);
|
||||
}
|
||||
};
|
||||
let mut surgeries = Surgeries::new(&app_syms, app_func_addresses);
|
||||
surgeries.append_text_sections(exec_data, &exec_obj, verbose);
|
||||
md.surgeries = surgeries.surgeries;
|
||||
|
||||
let data = match sec.uncompressed_data() {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
internal_error!("Failed to load text section, {:+x?}: {}", sec, err);
|
||||
}
|
||||
};
|
||||
let mut decoder = Decoder::with_ip(64, &data, sec.address(), DecoderOptions::NONE);
|
||||
let mut inst = Instruction::default();
|
||||
|
||||
while decoder.can_decode() {
|
||||
decoder.decode_out(&mut inst);
|
||||
|
||||
// Note: This gets really complex fast if we want to support more than basic calls/jumps.
|
||||
// A lot of them have to load addresses into registers/memory so we would have to discover that value.
|
||||
// Would probably require some static code analysis and would be impossible in some cases.
|
||||
// As an alternative we can leave in the calls to the plt, but change the plt to jmp to the static function.
|
||||
// That way any indirect call will just have the overhead of an extra jump.
|
||||
match inst.try_op_kind(0) {
|
||||
// Relative Offsets.
|
||||
Ok(OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64) => {
|
||||
let target = inst.near_branch_target();
|
||||
if let Some(func_name) = app_func_addresses.get(&target) {
|
||||
if compressed {
|
||||
internal_error!("Surgical linking does not work with compressed text sections: {:+x?}", sec);
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"Found branch from {:+x} to {:+x}({})",
|
||||
inst.ip(),
|
||||
target,
|
||||
func_name
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Double check these offsets are always correct.
|
||||
// We may need to do a custom offset based on opcode instead.
|
||||
let op_kind = inst.op_code().try_op_kind(0).unwrap();
|
||||
let op_size: u8 = match op_kind {
|
||||
OpCodeOperandKind::br16_1 | OpCodeOperandKind::br32_1 => 1,
|
||||
OpCodeOperandKind::br16_2 => 2,
|
||||
OpCodeOperandKind::br32_4 | OpCodeOperandKind::br64_4 => 4,
|
||||
_ => {
|
||||
internal_error!(
|
||||
"Ran into an unknown operand kind when analyzing branches: {:?}",
|
||||
op_kind
|
||||
);
|
||||
}
|
||||
};
|
||||
let offset = inst.next_ip() - op_size as u64 - sec.address() + file_offset;
|
||||
if verbose {
|
||||
println!(
|
||||
"\tNeed to surgically replace {} bytes at file offset {:+x}",
|
||||
op_size, offset,
|
||||
);
|
||||
println!(
|
||||
"\tIts current value is {:+x?}",
|
||||
&exec_data[offset as usize..(offset + op_size as u64) as usize]
|
||||
)
|
||||
}
|
||||
md.surgeries
|
||||
.get_mut(*func_name)
|
||||
.unwrap()
|
||||
.push(metadata::SurgeryEntry {
|
||||
file_offset: offset,
|
||||
virtual_offset: VirtualOffset::Relative(inst.next_ip()),
|
||||
size: op_size,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(OpKind::FarBranch16 | OpKind::FarBranch32) => {
|
||||
internal_error!(
|
||||
"Found branch type instruction that is not yet support: {:+x?}",
|
||||
inst
|
||||
);
|
||||
}
|
||||
Ok(_) => {
|
||||
if (inst.is_call_far_indirect()
|
||||
|| inst.is_call_near_indirect()
|
||||
|| inst.is_jmp_far_indirect()
|
||||
|| inst.is_jmp_near_indirect())
|
||||
&& !indirect_warning_given
|
||||
&& verbose
|
||||
{
|
||||
indirect_warning_given = true;
|
||||
println!();
|
||||
println!("Cannot analyaze through indirect jmp type instructions");
|
||||
println!("Most likely this is not a problem, but it could mean a loss in optimizations");
|
||||
println!();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
internal_error!("Failed to decode assembly: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let text_disassembly_duration = text_disassembly_start.elapsed();
|
||||
|
||||
let scanning_dynamic_deps_duration;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue