mirror of
https://github.com/Noratrieb/elven-forest.git
synced 2026-01-14 10:45:03 +01:00
cleanup
This commit is contained in:
parent
03a4287bee
commit
8aa4068b1b
5 changed files with 234 additions and 49 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
|
@ -2,6 +2,18 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f0778972c64420fdedc63f09919c8a88bda7b25135357fd25a5d9f3257e832"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.0"
|
||||
|
|
@ -26,6 +38,7 @@ dependencies = [
|
|||
name = "elven-parser"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"bytemuck",
|
||||
"memmap2",
|
||||
"thiserror",
|
||||
|
|
@ -37,6 +50,12 @@ version = "0.2.139"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.8"
|
||||
|
|
@ -46,6 +65,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
|
|
@ -64,6 +89,18 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bstr = "1.2.0"
|
||||
bytemuck = { version = "1.13.0", features = ["derive", "min_const_generics"] }
|
||||
memmap2 = "0.5.8"
|
||||
thiserror = "1.0.38"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ macro_rules! const_group_with_fmt {
|
|||
$(
|
||||
$value => f.write_str(stringify!($name)),
|
||||
)*
|
||||
a => write!(f, "Other {}: {a}", $group_name,)
|
||||
a => write!(f, "{}({a})", $group_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,24 @@ macro_rules! const_group_with_fmt {
|
|||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<$struct_name> for $ty {
|
||||
fn eq(&self, other: &$struct_name) -> bool {
|
||||
*self == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<$ty> for $struct_name {
|
||||
fn partial_cmp(&self, other: &$ty) -> Option<std::cmp::Ordering> {
|
||||
self.0.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<$struct_name> for $ty {
|
||||
fn partial_cmp(&self, other: &$struct_name) -> Option<std::cmp::Ordering> {
|
||||
self.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +63,7 @@ pub const SELFMAG: usize = 4;
|
|||
|
||||
pub const EI_CLASS: usize = 4; /* File class byte index */
|
||||
const_group_with_fmt! {
|
||||
pub struct Class(u8): "Class"
|
||||
pub struct Class(u8): "class"
|
||||
|
||||
pub const ELFCLASSNONE = 0; /* Invalid class */
|
||||
pub const ELFCLASS32 = 1; /* 32-bit objects */
|
||||
|
|
@ -55,7 +73,7 @@ pub const ELFCLASSNUM: u8 = 3;
|
|||
|
||||
pub const EI_DATA: usize = 5; /* Data encoding byte index */
|
||||
const_group_with_fmt! {
|
||||
pub struct Data(u8): "Data"
|
||||
pub struct Data(u8): "data"
|
||||
|
||||
pub const ELFDATANONE = 0; /* Invalid data encoding */
|
||||
pub const ELFDATA2LSB = 1; /* 2's complement, little endian */
|
||||
|
|
@ -108,21 +126,26 @@ pub const EV_NONE: u32 = 0;
|
|||
// Sections
|
||||
// ------------------
|
||||
|
||||
pub const SHN_UNDEF: u16 = 0; /* Undefined section */
|
||||
const_group_with_fmt! {
|
||||
pub struct SectionIdx(u16): "SHN"
|
||||
|
||||
pub const SHN_UNDEF = 0; /* Undefined section */
|
||||
pub const SHN_BEFORE = 0xff00; /* Order section before all others (Solaris). */
|
||||
pub const SHN_AFTER = 0xff01; /* Order section after all others (Solaris). */
|
||||
|
||||
pub const SHN_ABS = 0xfff1; /* Associated symbol is absolute */
|
||||
pub const SHN_COMMON = 0xfff2; /* Associated symbol is common */
|
||||
pub const SHN_XINDEX = 0xffff; /* Index is in extra table. */
|
||||
}
|
||||
pub const SHN_LORESERVE: u16 = 0xff00; /* Start of reserved indices */
|
||||
pub const SHN_LOPROC: u16 = 0xff00; /* Start of processor-specific */
|
||||
pub const SHN_BEFORE: u16 = 0xff00; /* Order section before all others (Solaris). */
|
||||
pub const SHN_AFTER: u16 = 0xff01; /* Order section after all others (Solaris). */
|
||||
pub const SHN_HIPROC: u16 = 0xff1f; /* End of processor-specific */
|
||||
pub const SHN_LOOS: u16 = 0xff20; /* Start of OS-specific */
|
||||
pub const SHN_HIOS: u16 = 0xff3f; /* End of OS-specific */
|
||||
pub const SHN_ABS: u16 = 0xfff1; /* Associated symbol is absolute */
|
||||
pub const SHN_COMMON: u16 = 0xfff2; /* Associated symbol is common */
|
||||
pub const SHN_XINDEX: u16 = 0xffff; /* Index is in extra table. */
|
||||
pub const SHN_HIRESERVE: u16 = 0xffff; /* End of reserved indices */
|
||||
|
||||
const_group_with_fmt! {
|
||||
pub struct ShType(u32): "Section header type"
|
||||
pub struct ShType(u32): "SHT"
|
||||
|
||||
pub const SHT_NULL = 0; /* Section header table entry unused */
|
||||
pub const SHT_PROGBITS = 1; /* Program data */
|
||||
|
|
@ -163,8 +186,58 @@ pub const SHT_LOSUNW: u32 = 0x6ffffffa; /* Sun-specific low bound. */
|
|||
pub const SHT_HISUNW: u32 = 0x6fffffff; /* Sun-specific high bound. */
|
||||
pub const SHT_HIOS: u32 = 0x6fffffff; /* End OS-specific type */
|
||||
|
||||
// ------------------
|
||||
// Symbols
|
||||
// ------------------
|
||||
|
||||
const_group_with_fmt! {
|
||||
pub struct RX86_64(u32): "x86_64 Relocation type"
|
||||
pub struct SymbolType(u8): "STT"
|
||||
|
||||
pub const STT_NOTYPE = 0; /* Symbol type is unspecified */
|
||||
pub const STT_OBJECT = 1; /* Symbol is a data object */
|
||||
pub const STT_FUNC = 2; /* Symbol is a code object */
|
||||
pub const STT_SECTION = 3; /* Symbol associated with a section */
|
||||
pub const STT_FILE = 4; /* Symbol's name is file name */
|
||||
pub const STT_COMMON = 5; /* Symbol is a common data object */
|
||||
pub const STT_TLS = 6; /* Symbol is thread-local data object*/
|
||||
pub const STT_NUM = 7; /* Number of defined types. */
|
||||
pub const STT_GNU_IFUNC = 10; /* Symbol is indirect code object */
|
||||
pub const STT_HIOS = 12; /* End of OS-specific */
|
||||
pub const STT_LOPROC = 13; /* Start of processor-specific */
|
||||
pub const STT_HIPROC = 15; /* End of processor-specific */
|
||||
}
|
||||
pub const STT_LOOS: u32 = 10; /* Start of OS-specific */
|
||||
|
||||
const_group_with_fmt! {
|
||||
pub struct SymbolBinding(u8): "STB"
|
||||
|
||||
pub const STB_LOCAL = 0; /* Local symbol */
|
||||
pub const STB_GLOBAL = 1; /* Global symbol */
|
||||
pub const STB_WEAK = 2; /* Weak symbol */
|
||||
pub const STB_NUM = 3; /* Number of defined types. */
|
||||
pub const STB_GNU_UNIQUE = 10; /* Unique symbol. */
|
||||
pub const STB_HIOS = 12; /* End of OS-specific */
|
||||
pub const STB_LOPROC = 13; /* Start of processor-specific */
|
||||
pub const STB_HIPROC = 15; /* End of processor-specific */
|
||||
}
|
||||
pub const STB_LOOS: u8 = 10; /* Start of OS-specific */
|
||||
|
||||
/* Symbol visibility specification encoded in the st_other field. */
|
||||
const_group_with_fmt! {
|
||||
pub struct SymbolVisibility(u8): "STV"
|
||||
|
||||
pub const STV_DEFAULT = 0; /* Default symbol visibility rules */
|
||||
pub const STV_INTERNAL = 1; /* Processor specific hidden class */
|
||||
pub const STV_HIDDEN = 2; /* Sym unavailable in other modules */
|
||||
pub const STV_PROTECTED = 3; /* Not preemptible, not exported */
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Relocations
|
||||
// ------------------
|
||||
|
||||
const_group_with_fmt! {
|
||||
pub struct RX86_64(u32): "R_X86_64"
|
||||
|
||||
pub const R_X86_64_NONE = 0; /* No reloc */
|
||||
pub const R_X86_64_64 = 1; /* Direct 64 bit */
|
||||
|
|
@ -211,3 +284,9 @@ const_group_with_fmt! {
|
|||
pub const R_X86_64_REX_GOTPCRELX = 42; /* Load from 32 bit signed pc relative offset to GOT entry with REX prefix, relaxable. */
|
||||
pub const R_X86_64_NUM = 43;
|
||||
}
|
||||
|
||||
impl SectionIdx {
|
||||
pub fn usize(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,10 @@
|
|||
//!
|
||||
//! See https://man7.org/linux/man-pages/man5/elf.5.html
|
||||
|
||||
pub mod consts;
|
||||
use consts as c;
|
||||
use crate::consts as c;
|
||||
use bstr::BStr;
|
||||
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fmt::Debug,
|
||||
mem,
|
||||
ops::{self},
|
||||
slice::SliceIndex,
|
||||
};
|
||||
use std::{fmt::Debug, mem, ops, slice::SliceIndex, string};
|
||||
|
||||
use bytemuck::{Pod, PodCastError, Zeroable};
|
||||
|
||||
|
|
@ -32,11 +26,15 @@ impl Offset {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)]
|
||||
#[repr(transparent)]
|
||||
pub struct Section(pub u16);
|
||||
pub struct ShStringIdx(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)]
|
||||
#[repr(transparent)]
|
||||
pub struct Versym(pub u16);
|
||||
pub struct StringIdx(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)]
|
||||
#[repr(transparent)]
|
||||
pub struct SymIdx(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum ElfParseError {
|
||||
|
|
@ -58,6 +56,8 @@ pub enum ElfParseError {
|
|||
NoStringNulTerm(usize),
|
||||
#[error("The SHT_SYMTAB section was not found")]
|
||||
SymtabNotFound,
|
||||
#[error("The section with the name {0:?} was not found")]
|
||||
SectionNotFound(std::result::Result<string::String, Vec<u8>>),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, ElfParseError>;
|
||||
|
|
@ -84,7 +84,7 @@ pub struct ElfHeader {
|
|||
pub phnum: u16,
|
||||
pub shentsize: u16,
|
||||
pub shnum: u16,
|
||||
pub shstrndex: u16,
|
||||
pub shstrndex: c::SectionIdx,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
|
|
@ -117,7 +117,7 @@ pub struct Phdr {
|
|||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Shdr {
|
||||
pub name: u32,
|
||||
pub name: ShStringIdx,
|
||||
pub r#type: c::ShType,
|
||||
pub flags: u64,
|
||||
pub addr: Addr,
|
||||
|
|
@ -132,14 +132,34 @@ pub struct Shdr {
|
|||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Sym {
|
||||
pub name: u32,
|
||||
pub info: u8,
|
||||
pub other: u8,
|
||||
pub shndx: u16,
|
||||
pub name: StringIdx,
|
||||
pub info: SymInfo,
|
||||
pub other: c::SymbolVisibility,
|
||||
pub shndx: c::SectionIdx,
|
||||
pub value: Addr,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(transparent)]
|
||||
pub struct SymInfo(pub u8);
|
||||
|
||||
impl SymInfo {
|
||||
pub fn r#type(self) -> c::SymbolType {
|
||||
c::SymbolType(self.0 & 0xf)
|
||||
}
|
||||
|
||||
pub fn binding(self) -> c::SymbolBinding {
|
||||
c::SymbolBinding(self.0 >> 4)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SymInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?},{:?}", self.r#type(), self.binding())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Rel {
|
||||
|
|
@ -157,11 +177,11 @@ pub struct Rela {
|
|||
|
||||
#[derive(Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(transparent)]
|
||||
pub struct RelInfo(u64);
|
||||
pub struct RelInfo(pub u64);
|
||||
|
||||
impl RelInfo {
|
||||
pub fn sym(&self) -> u32 {
|
||||
(self.0 >> 32) as u32
|
||||
pub fn sym(&self) -> SymIdx {
|
||||
SymIdx((self.0 >> 32) as u32)
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> u32 {
|
||||
|
|
@ -171,7 +191,7 @@ impl RelInfo {
|
|||
|
||||
impl Debug for RelInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?} @ {}", c::RX86_64(self.r#type()), self.sym())
|
||||
write!(f, "{:?} @ {}", c::RX86_64(self.r#type()), self.sym().0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +230,7 @@ impl<'a> Elf<'a> {
|
|||
|
||||
let off = header.phoff.usize();
|
||||
load_slice(
|
||||
&self.data.get_elf(off.., "program header offset")?,
|
||||
self.data.get_elf(off.., "program header offset")?,
|
||||
header.phnum.into(),
|
||||
)
|
||||
}
|
||||
|
|
@ -232,14 +252,27 @@ impl<'a> Elf<'a> {
|
|||
}
|
||||
let off = header.shoff.usize();
|
||||
load_slice(
|
||||
&self.data.get_elf(off.., "sectoin header offset")?,
|
||||
self.data.get_elf(off.., "sectoin header offset")?,
|
||||
header.shnum.into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn section_header(&self, idx: usize) -> Result<&Shdr> {
|
||||
pub fn section_header(&self, idx: c::SectionIdx) -> Result<&Shdr> {
|
||||
let sections = self.section_headers()?;
|
||||
sections.get_elf(idx, "section number")
|
||||
sections.get_elf(idx.usize(), "section number")
|
||||
}
|
||||
|
||||
pub fn section_header_by_name(&self, name: &[u8]) -> Result<&Shdr> {
|
||||
let sections = self.section_headers()?;
|
||||
for sh in sections {
|
||||
if self.sh_string(sh.name)? == name {
|
||||
return Ok(sh);
|
||||
}
|
||||
}
|
||||
let name = name.to_vec();
|
||||
Err(ElfParseError::SectionNotFound(
|
||||
string::String::from_utf8(name).map_err(|err| err.into_bytes()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn section_content(&self, sh: &Shdr) -> Result<&[u8]> {
|
||||
|
|
@ -247,13 +280,12 @@ impl<'a> Elf<'a> {
|
|||
return Ok(&[]);
|
||||
}
|
||||
|
||||
Ok(&self
|
||||
.data
|
||||
self.data
|
||||
.get_elf(sh.offset.usize().., "section offset")?
|
||||
.get_elf(..(sh.size as usize), "section size")?)
|
||||
.get_elf(..(sh.size as usize), "section size")
|
||||
}
|
||||
|
||||
pub fn str_table(&self) -> Result<&[u8]> {
|
||||
pub fn sh_str_table(&self) -> Result<&[u8]> {
|
||||
let header = self.header()?;
|
||||
let shstrndex = header.shstrndex;
|
||||
|
||||
|
|
@ -271,18 +303,35 @@ impl<'a> Elf<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
let strtab_header = self.section_header(shstrndex as usize)?;
|
||||
let strtab_header = self.section_header(shstrndex)?;
|
||||
self.section_content(strtab_header)
|
||||
}
|
||||
|
||||
pub fn string(&self, idx: usize) -> Result<&CStr> {
|
||||
pub fn str_table(&self) -> Result<&[u8]> {
|
||||
let sh = self.section_header_by_name(b".strtab")?;
|
||||
self.section_content(sh)
|
||||
}
|
||||
|
||||
pub fn sh_string(&self, idx: ShStringIdx) -> Result<&BStr> {
|
||||
let idx = idx.0 as usize;
|
||||
let str_table = self.sh_str_table()?;
|
||||
let indexed = str_table.get_elf(idx.., "string offset")?;
|
||||
let end = indexed
|
||||
.iter()
|
||||
.position(|&c| c == b'\0')
|
||||
.ok_or(ElfParseError::NoStringNulTerm(idx))?;
|
||||
Ok(BStr::new(&indexed[..end]))
|
||||
}
|
||||
|
||||
pub fn string(&self, idx: StringIdx) -> Result<&BStr> {
|
||||
let idx = idx.0 as usize;
|
||||
let str_table = self.str_table()?;
|
||||
let indexed = str_table.get_elf(idx.., "string offset")?;
|
||||
let end = indexed
|
||||
.iter()
|
||||
.position(|&c| c == b'\0')
|
||||
.ok_or(ElfParseError::NoStringNulTerm(idx))?;
|
||||
Ok(CStr::from_bytes_with_nul(&indexed[..=end]).unwrap())
|
||||
Ok(BStr::new(&indexed[..end]))
|
||||
}
|
||||
|
||||
pub fn relas(&self) -> Result<impl Iterator<Item = (&Shdr, &Rela)>> {
|
||||
|
|
@ -311,6 +360,11 @@ impl<'a> Elf<'a> {
|
|||
|
||||
load_slice(data, data.len() / mem::size_of::<Sym>())
|
||||
}
|
||||
|
||||
pub fn symbol(&self, idx: SymIdx) -> Result<&Sym> {
|
||||
let idx = idx.0 as usize;
|
||||
self.symbols()?.get_elf(idx, "symbol index")
|
||||
}
|
||||
}
|
||||
|
||||
fn load_ref<T: Pod>(data: &[u8]) -> Result<&T> {
|
||||
|
|
@ -417,7 +471,7 @@ mod tests {
|
|||
elf.section_headers()?;
|
||||
|
||||
for section in elf.section_headers()? {
|
||||
let name = elf.string(section.name as usize)?.to_str().unwrap();
|
||||
let name = elf.sh_string(section.name)?.to_string();
|
||||
println!("{name:20} {:5} {:?}", section.size, section.r#type);
|
||||
}
|
||||
|
||||
|
|
@ -439,17 +493,30 @@ mod tests {
|
|||
elf.program_headers()?;
|
||||
elf.section_headers()?;
|
||||
|
||||
println!("Sections:\n");
|
||||
|
||||
for sh in elf.section_headers()? {
|
||||
let name = elf.string(sh.name as usize)?.to_str().unwrap();
|
||||
let name = elf.sh_string(sh.name)?.to_string();
|
||||
println!("{name:20} {:5} {:?}", sh.size, sh.r#type);
|
||||
}
|
||||
|
||||
println!("Relocations:");
|
||||
println!("Relocations:\n");
|
||||
|
||||
println!("{:20} {:10} {}", "Section", "Symbol", "Relocation");
|
||||
|
||||
let mut has_puts = false;
|
||||
for (sh, rela) in elf.relas()? {
|
||||
let section_name = elf.string(sh.name as usize)?.to_str().unwrap();
|
||||
println!("{section_name:20} {:?}", rela);
|
||||
let section_name = elf.sh_string(sh.name)?.to_string();
|
||||
let sym = elf.symbol(rela.info.sym())?;
|
||||
let sym_name = elf.string(sym.name)?.to_string();
|
||||
println!("{section_name:20} {sym_name:10} {rela:?}");
|
||||
|
||||
if sym_name == "puts" {
|
||||
has_puts = true;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(has_puts, "puts symbol not found");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
pub mod raw;
|
||||
pub mod consts;
|
||||
pub mod defs;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue