diff options
-rw-r--r-- | src/linker.rs | 186 | ||||
-rw-r--r-- | src/main.rs | 34 | ||||
-rw-r--r-- | test.c | 10 | ||||
-rw-r--r-- | tests/basic.c | 10 | ||||
-rw-r--r-- | tests/cpp.cpp | 10 |
5 files changed, 203 insertions, 47 deletions
diff --git a/src/linker.rs b/src/linker.rs index 5ff1ca0..9500495 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -17,6 +17,7 @@ Example usage: let mut linker = Linker::new(); linker.add_input("main.o")?; linker.add_input("libc.so.6")?; +linker.add_input("libstdc++.so.6")?; linker.link_to_file("a.out", "entry")?; ``` @@ -113,10 +114,19 @@ impl From<&LinkWarning> for String { /// error produced by [Linker::add_object] pub enum ObjectError { + IO(io::Error), /// ELF format error Elf(elf::Error), /// wrong type of ELF file BadType, + /// compile command failed + CommandFailed(std::process::ExitStatus), +} + +impl From<io::Error> for ObjectError { + fn from(e: io::Error) -> Self { + Self::IO(e) + } } impl From<elf::Error> for ObjectError { @@ -135,10 +145,10 @@ impl fmt::Display for ObjectError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { use ObjectError::*; match self { - // Display for UnexpectedEof *should* be this but is less clear - // ("failed to fill whole buffer") + IO(e) => write!(f, "{e}"), Elf(e) => write!(f, "{e}"), BadType => write!(f, "wrong type of ELF file (not an object file)"), + CommandFailed(status) => write!(f, "command failed: {status}"), } } } @@ -324,6 +334,14 @@ struct Relocation { pub struct Linker<'a> { symbols: Symbols, symbol_names: SymbolNames, + /// C compiler + cc: String, + /// C compiler flags + cflags: Vec<String>, + /// C++ compiler + cxx: String, + /// C++ compiler flags + cxxflags: Vec<String>, relocations: Vec<Relocation>, /// `sources[n]` = name of source corresponding to [SourceId]`(n)`. /// These aren't necessarily valid paths. They're just names @@ -726,6 +744,9 @@ impl LinkerOutput { } impl<'a> Linker<'a> { + pub const DEFAULT_CFLAGS: [&str; 5] = ["-Wall", "-Os", "-m32", "-fno-pic", "-c"]; + pub const DEFAULT_CXXFLAGS: [&str; 5] = Self::DEFAULT_CFLAGS; + fn default_warning_handler(warning: LinkWarning) { eprintln!("warning: {warning}"); } @@ -741,12 +762,42 @@ impl<'a> Linker<'a> { symbols: Symbols::new(), symbol_names: SymbolNames::new(), bss_size: 0, + cc: "cc".into(), + cxx: "c++".into(), + cflags: Self::DEFAULT_CFLAGS.iter().map(|&r| r.into()).collect(), + cxxflags: Self::DEFAULT_CXXFLAGS.iter().map(|&r| r.into()).collect(), relocations: vec![], sources: vec![], libraries: vec![], warn: Box::new(Self::default_warning_handler), } } + + /// Set the C compiler. + pub fn set_cc(&mut self, cc: &str) { + self.cc = cc.into(); + } + + /// Set the C compiler flags. + /// + /// These had better include something like `-c` and + /// something like `-fno-pic`. + pub fn set_cflags(&mut self, cflags: &[String]) { + self.cflags = cflags.to_vec(); + } + + /// Set the C++ compiler. + pub fn set_cxx(&mut self, cxx: &str) { + self.cxx = cxx.into(); + } + + /// Set the C++ compiler flags. + /// + /// These had better include something like `-c` and + /// something like `-fno-pic`. + pub fn set_cxxflags(&mut self, cxxflags: &[String]) { + self.cxxflags = cxxflags.to_vec(); + } /// Get name of source file. fn source_name(&self, id: SourceId) -> &str { @@ -848,12 +899,104 @@ impl<'a> Linker<'a> { Ok(()) } + pub fn add_object_from_file(&mut self, path: impl AsRef<path::Path>) -> Result<(), ObjectError> { + let path = path.as_ref(); + let file = fs::File::open(path)?; + let mut file = io::BufReader::new(file); + self.add_object(&path.to_string_lossy(), &mut file) + } + /// Add a dynamic library (.so). `name` can be a full path or /// something like "libc.so.6" --- any string you would pass to `dlopen`. pub fn add_dynamic_library(&mut self, name: &str) -> Result<(), ObjectError> { self.libraries.push(name.into()); Ok(()) } + + fn compile(&self, compiler: &str, flags: &[String], path: &str) -> Result<String, ObjectError> { + use std::process::Command; + + let ext_idx = path.rfind('.').unwrap_or(path.len()); + let output_filename = path[..ext_idx].to_string() + ".o"; + + let status = Command::new(compiler) + .args(flags) + .arg(path) + .arg("-o") + .arg(&output_filename) + .status()?; + if status.success() { + Ok(output_filename) + } else { + Err(ObjectError::CommandFailed(status)) + } + } + + /// Add a C file (.c). This calls out to an external C compiler. + pub fn add_c(&mut self, path: &str) -> Result<(), ObjectError> { + let output = self.compile(&self.cc, &self.cflags, path)?; + self.add_object_from_file(&output) + } + + /// Add a C++ file (.cpp/.cc/etc). This calls out to an external C++ compiler. + pub fn add_cpp(&mut self, path: &str) -> Result<(), ObjectError> { + let output = self.compile(&self.cxx, &self.cxxflags, path)?; + self.add_object_from_file(&output) + } + + /// Easy input API. + /// Infers the file type of input, and calls the appropriate function (e.g. [Self::add_object]). + pub fn add_input(&mut self, input: &str) -> Result<(), String> { + enum FileType { + Object, + DynamicLibrary, + C, + CPlusPlus, + Other, + } + + use FileType::*; + + fn file_type(input: &str) -> FileType { + if input.ends_with(".o") { + return Object; + } + if input.ends_with(".c") { + return C; + } + if input.ends_with(".cpp") || input.ends_with(".cc") || input.ends_with(".cxx") + || input.ends_with(".C") { + return CPlusPlus; + } + if input.ends_with(".so") { + return DynamicLibrary; + } + if input.contains(".so.") { + // e.g. libc.so.6, some_library.so.12.7.3 + return DynamicLibrary; + } + Other + } + + match file_type(input) { + Object => { + self.add_object_from_file(input) + .map_err(|e| format!("Failed to process object file {input}: {e}")) + } + C => { + self.add_c(input) + .map_err(|e| format!("Failed to process C file {input}: {e}")) + }, + CPlusPlus => { + self.add_cpp(input) + .map_err(|e| format!("Failed to process C++ file {input}: {e}")) + }, + DynamicLibrary => self + .add_dynamic_library(input) + .map_err(|e| format!("Failed to process library file {input}: {e}")), + Other => Err(format!("Unrecognized file type: {input}")), + } + } /// Get name of symbol. fn symbol_name_str(&self, id: SymbolName) -> &str { @@ -975,45 +1118,6 @@ impl<'a> Linker<'a> { Ok(()) } - /// Easy input API. - /// Infers the file type of input, and calls the appropriate function (e.g. [Self::add_object]). - pub fn add_input(&mut self, input: &str) -> Result<(), String> { - enum FileType { - Object, - DynamicLibrary, - Other, - } - - use FileType::*; - - fn file_type(input: &str) -> FileType { - if input.ends_with(".o") { - return Object; - } - if input.ends_with(".so") { - return DynamicLibrary; - } - if input.contains(".so.") { - // e.g. libc.so.6, some_library.so.12.7.3 - return DynamicLibrary; - } - Other - } - - match file_type(input) { - Object => { - let file = - fs::File::open(input).map_err(|e| format!("Couldn't open {input}: {e}"))?; - let mut file = io::BufReader::new(file); - self.add_object(input, &mut file) - .map_err(|e| format!("Failed to process object file {input}: {e}")) - } - DynamicLibrary => self - .add_dynamic_library(input) - .map_err(|e| format!("Failed to process library file {input}: {e}")), - Other => Err(format!("Unrecognized file type: {input}")), - } - } /// Add data for a symbol. /// We don't want to link unused symbols. diff --git a/src/main.rs b/src/main.rs index 2526716..ed9c8ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ extern crate clap; +use std::env; use clap::Parser; #[cfg(target_endian = "big")] @@ -27,6 +28,11 @@ struct Args { /// This makes the executable smaller. #[arg(long = "no-stdlib", default_value_t = false)] no_std_lib: bool, + /// If set, the program will be linked against libstdc++. + /// + /// This is needed when using the standard template library. + #[arg(long = "stdc++", default_value_t = false)] + std_cpp: bool, /// Output executable path. #[arg(short = 'o', long = "output", default_value = "a.out")] output: String, @@ -36,6 +42,29 @@ struct Args { /// :3 #[arg(long = "nya")] nya: bool, + /// C compiler + /// + /// First this is checked, then the `CC` environment variable, + /// then if both aren't set, (/usr/bin/)cc is used. + #[arg(long = "cc", default_value = "auto")] + cc: String, + /// C compiler flags + /// + /// The C compiler is invoked using `(cc) (cflags) (C file) -o (object file)` + #[arg(long = "cflags", default_values = linker::Linker::DEFAULT_CFLAGS)] + cflags: Vec<String>, +} + +impl Args { + fn get_c_compiler(&self) -> String { + if self.cc != "auto" { + self.cc.clone() + } else if let Ok(env_cc) = env::var("CC") { + env_cc + } else { + "cc".into() + } + } } fn main_() -> Result<(), String> { @@ -49,6 +78,8 @@ fn main_() -> Result<(), String> { let inputs = &args.inputs; let mut linker = linker::Linker::new(); + + linker.set_cc(&args.get_c_compiler()); let warning_handler = |w| { // termcolor is a goddamn nightmare to use @@ -70,6 +101,9 @@ fn main_() -> Result<(), String> { if !args.no_std_lib { linker.add_input("libc.so.6")?; } + if args.std_cpp { + linker.add_input("libstdc++.so.6")?; + } for input in inputs.iter() { linker.add_input(input)?; @@ -1,12 +1,10 @@ #include <stdio.h> #include <stdlib.h> -extern int errno; -int main() { - errno = 0; - fopen("zzz","r"); - printf("%d\n",errno); - //printf("hello\n"); +int my_number; +void entry() { + my_number = 137; + printf("%d\n",my_number); exit(0); } diff --git a/tests/basic.c b/tests/basic.c new file mode 100644 index 0000000..b8a6d61 --- /dev/null +++ b/tests/basic.c @@ -0,0 +1,10 @@ +#include <stdio.h> +#include <stdlib.h> + +int my_number; +void entry() { + my_number = 137; + printf("%d\n",my_number); + exit(0); + +} diff --git a/tests/cpp.cpp b/tests/cpp.cpp new file mode 100644 index 0000000..5da16e5 --- /dev/null +++ b/tests/cpp.cpp @@ -0,0 +1,10 @@ +#include <vector> +#include <iostream> +#include <algorithm> +#include <climits> + +extern "C" int main(void) { + std::vector<int> numbers = {}; + + exit((int)(long)&numbers); +} |