mirror of
https://codeberg.org/unspeaker/vestal.git
synced 2025-12-06 12:56:41 +01:00
report load addr overlaps
This commit is contained in:
parent
5490fef835
commit
f5a4ce9116
9 changed files with 260 additions and 21 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
target
|
target
|
||||||
*.dll
|
*.dll
|
||||||
*.exe
|
*.exe
|
||||||
|
bin/
|
||||||
|
|
|
||||||
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -88,6 +88,17 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "binary-layout"
|
||||||
|
version = "4.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66c7e8da2156abef3421f6226ef339ade8c0d157ec50932d5e624f1c6a5127b4"
|
||||||
|
dependencies = [
|
||||||
|
"doc-comment",
|
||||||
|
"paste",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
|
@ -216,6 +227,12 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
|
@ -477,6 +494,12 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkbuffer"
|
name = "pkbuffer"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
@ -658,6 +681,26 @@ dependencies = [
|
||||||
"jack",
|
"jack",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twox-hash"
|
name = "twox-hash"
|
||||||
version = "1.6.3"
|
version = "1.6.3"
|
||||||
|
|
@ -696,6 +739,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
name = "vestal"
|
name = "vestal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"binary-layout",
|
||||||
"clap",
|
"clap",
|
||||||
"exe",
|
"exe",
|
||||||
"hexy",
|
"hexy",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pretty-hex = "0.4.1"
|
||||||
exe = "0.5.6"
|
exe = "0.5.6"
|
||||||
iced-x86 = "1.21.0"
|
iced-x86 = "1.21.0"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
binary-layout = "4.0.2"
|
||||||
#elf = "0.7.4"
|
#elf = "0.7.4"
|
||||||
#goblin = "0.9.3"
|
#goblin = "0.9.3"
|
||||||
#lancelot = "0.9.7"
|
#lancelot = "0.9.7"
|
||||||
|
|
|
||||||
15
crates/vestal/src/bang.rs
Normal file
15
crates/vestal/src/bang.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// You can manually patch DLLs by prepending
|
||||||
|
/// a `#!/usr/bin/env vestal` line to them.
|
||||||
|
pub fn slice_shebang (buffer: &[u8]) -> (Arc<[u8]>, Arc<[u8]>) {
|
||||||
|
if buffer.get(0) == Some(&b'#') && buffer.get(1) == Some(&b'!') {
|
||||||
|
if let Some((bang, data)) = buffer.split_once(|x|*x==0x0a) {
|
||||||
|
(bang.to_vec().into(), data.to_vec().into())
|
||||||
|
} else {
|
||||||
|
(buffer.to_vec().into(), vec![].into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(vec![].into(), buffer.to_vec().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
165
crates/vestal/src/link.rs
Normal file
165
crates/vestal/src/link.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
//! Create a minimal ELF file manually.
|
||||||
|
//!
|
||||||
|
//! Based on reading the [ELF-64
|
||||||
|
//! standard](https://uclibc.org/docs/elf-64-gen.pdf) and the [x86-64
|
||||||
|
//! architecture supplement](https://uclibc.org/docs/psABI-x86_64.pdf) (for the
|
||||||
|
//! value `EM_X86_64`, specific to x86-64).
|
||||||
|
//!
|
||||||
|
//! Also learned from the classic blog post "[A Whirlwind Tutorial on Creating
|
||||||
|
//! Really Teensy Elf Executables for
|
||||||
|
//! Linux](https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html)" and
|
||||||
|
//! the code in <https://github.com/AjayBrahmakshatriya/minimal-elf/>.
|
||||||
|
|
||||||
|
// from https://github.com/tchajed/minimal-elf/blob/main/src/lib.rs
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
use binary_layout::prelude::*;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs::OpenOptions, os::unix::prelude::OpenOptionsExt};
|
||||||
|
|
||||||
|
type Elf64_Addr = u64;
|
||||||
|
type Elf64_Off = u64;
|
||||||
|
type Elf64_Half = u16;
|
||||||
|
type Elf64_Word = u32;
|
||||||
|
// type Elf64_Sword = i32;
|
||||||
|
type Elf64_Xword = u64;
|
||||||
|
// type Elf64_Sxword = i64;
|
||||||
|
|
||||||
|
pub const VADDR: u64 = 0x400000;
|
||||||
|
const PROGRAM_OFFSET: u64 = {
|
||||||
|
// XXX: manually implement unwrap since it isn't stable as a const fn
|
||||||
|
let sz1 = match elf64_hdr::SIZE {
|
||||||
|
Some(s) => s,
|
||||||
|
None => panic!("unsized"),
|
||||||
|
};
|
||||||
|
let sz2 = match elf64_phdr::SIZE {
|
||||||
|
Some(s) => s,
|
||||||
|
None => panic!("unsized"),
|
||||||
|
};
|
||||||
|
(sz1 + sz2) as u64
|
||||||
|
};
|
||||||
|
|
||||||
|
define_layout!(elf64_file, LittleEndian, {
|
||||||
|
hdr: elf64_hdr::NestedView,
|
||||||
|
phdr: elf64_phdr::NestedView,
|
||||||
|
program: [u8],
|
||||||
|
});
|
||||||
|
define_layout!(elf64_hdr, LittleEndian, {
|
||||||
|
ident: elf64_ident::NestedView,
|
||||||
|
_type: Elf64_Half,
|
||||||
|
machine: Elf64_Half,
|
||||||
|
version: Elf64_Word,
|
||||||
|
entry: Elf64_Addr, // virtual address of entry point
|
||||||
|
phoff: Elf64_Off, // program header
|
||||||
|
shoff: Elf64_Off, // section header
|
||||||
|
flags: Elf64_Word, // processor-specific
|
||||||
|
ehsize: Elf64_Half,
|
||||||
|
phentsize: Elf64_Half,
|
||||||
|
phnum: Elf64_Half, // number of program header entries
|
||||||
|
shentsize: Elf64_Half, // size of section header entry
|
||||||
|
shnum: Elf64_Half, // number of section header entries
|
||||||
|
shstrndx: Elf64_Half, // section name string table index
|
||||||
|
});
|
||||||
|
define_layout!(elf64_phdr, LittleEndian, {
|
||||||
|
_type: Elf64_Word,
|
||||||
|
flags: Elf64_Word,
|
||||||
|
offset: Elf64_Off,
|
||||||
|
vaddr: Elf64_Addr,
|
||||||
|
paddr: Elf64_Addr,
|
||||||
|
filesz: Elf64_Xword,
|
||||||
|
memsz: Elf64_Xword,
|
||||||
|
align: Elf64_Xword,
|
||||||
|
});
|
||||||
|
define_layout!(elf64_ident, LittleEndian, {
|
||||||
|
mag: [u8; 4],
|
||||||
|
class: u8,
|
||||||
|
data: u8,
|
||||||
|
version: u8,
|
||||||
|
os_abi: u8,
|
||||||
|
abi_version: u8,
|
||||||
|
pad: [u8; 7],
|
||||||
|
});
|
||||||
|
fn set_ident<S: AsRef<[u8]> + AsMut<[u8]>>(mut view: elf64_ident::View<S>) {
|
||||||
|
view.mag_mut()
|
||||||
|
.copy_from_slice(&[0x7f, 'E' as u8, 'L' as u8, 'F' as u8]);
|
||||||
|
view.class_mut().write(2); // class: ELFCLASS64
|
||||||
|
view.data_mut().write(1); // data encoding: ELFDATA2LSB
|
||||||
|
view.version_mut().write(1); // file version: EV_CURRENT
|
||||||
|
view.os_abi_mut().write(0); // OS/ABI identification: System V
|
||||||
|
view.abi_version_mut().write(0); // ABI version: System V third edition
|
||||||
|
view.pad_mut().copy_from_slice(&[0u8; 7]);
|
||||||
|
}
|
||||||
|
fn set_elf64_hdr<S: AsRef<[u8]> + AsMut<[u8]>>(mut view: elf64_hdr::View<S>) {
|
||||||
|
set_ident(view.ident_mut());
|
||||||
|
view._type_mut().write(2); // ET_EXEC
|
||||||
|
view.machine_mut().write(62); // EM_X86_64
|
||||||
|
view.version_mut().write(1); // EV_CURRENT
|
||||||
|
view.entry_mut().write(VADDR + PROGRAM_OFFSET);
|
||||||
|
view.phoff_mut().write(elf64_hdr::SIZE.unwrap() as u64);
|
||||||
|
view.flags_mut().write(0); // no processor-specific flags
|
||||||
|
view.ehsize_mut().write(elf64_hdr::SIZE.unwrap() as u16);
|
||||||
|
view.phentsize_mut().write(elf64_phdr::SIZE.unwrap() as u16);
|
||||||
|
view.phnum_mut().write(1);
|
||||||
|
}
|
||||||
|
fn set_elf64_phdr<S>(mut view: elf64_phdr::View<S>, program_size: u64)
|
||||||
|
where
|
||||||
|
S: AsRef<[u8]> + AsMut<[u8]>,
|
||||||
|
{
|
||||||
|
view._type_mut().write(1); // PT_LOAD
|
||||||
|
view.flags_mut().write(0x1 | 0x2 | 0x4); // PF_X | PF_W | PF_R
|
||||||
|
|
||||||
|
// location of segment in file
|
||||||
|
let offset = (elf64_hdr::SIZE.unwrap() + elf64_phdr::SIZE.unwrap()) as u64;
|
||||||
|
view.offset_mut().write(offset);
|
||||||
|
// virtual address of segment
|
||||||
|
view.vaddr_mut().write(VADDR + PROGRAM_OFFSET);
|
||||||
|
|
||||||
|
view.filesz_mut().write(program_size);
|
||||||
|
view.memsz_mut().write(program_size);
|
||||||
|
view.align_mut().write(4096);
|
||||||
|
}
|
||||||
|
pub fn create_elf(program: &[u8]) -> Vec<u8> {
|
||||||
|
let hdr_sz = elf64_hdr::SIZE.unwrap();
|
||||||
|
let phdr_sz = elf64_phdr::SIZE.unwrap();
|
||||||
|
let mut buf = vec![0u8; hdr_sz + phdr_sz + program.len()];
|
||||||
|
let mut file = elf64_file::View::new(&mut buf);
|
||||||
|
set_elf64_hdr(file.hdr_mut());
|
||||||
|
set_elf64_phdr(file.phdr_mut(), program.len() as u64);
|
||||||
|
file.program_mut().copy_from_slice(program);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
//fn create_program() -> Vec<u8> {
|
||||||
|
//use iced_x86::code_asm::*;
|
||||||
|
//let f = || -> Result<_, IcedError> {
|
||||||
|
//let mut a = CodeAssembler::new(64)?;
|
||||||
|
//// push + pop is 2+1 bytes, which is slightly shorter than even mov(eax, 60)
|
||||||
|
//a.push(60)?;
|
||||||
|
//a.pop(rax)?;
|
||||||
|
//// a.mov(eax, 60)?;
|
||||||
|
//// zero edi in two bytes
|
||||||
|
//a.xor(edi, edi)?;
|
||||||
|
//a.syscall()?;
|
||||||
|
//let bytes = a.assemble(VADDR)?;
|
||||||
|
//Ok(bytes)
|
||||||
|
//};
|
||||||
|
//f().unwrap()
|
||||||
|
//}
|
||||||
|
#[cfg(test)] #[test] fn test_ident_size_ok() {
|
||||||
|
// XXX: could be a static assertion but Option<>::unwrap() is not a
|
||||||
|
// const_fn
|
||||||
|
assert_eq!(16, elf64_ident::SIZE.unwrap());
|
||||||
|
}
|
||||||
|
//#[cfg(test)] #[test] fn test_create_program() {
|
||||||
|
//let program = create_program();
|
||||||
|
//assert_eq!(7, program.len());
|
||||||
|
//}
|
||||||
|
//pub fn write_elf<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
|
||||||
|
//let buf = create_elf(&create_program());
|
||||||
|
//let mut options = OpenOptions::new();
|
||||||
|
//options.write(true).create(true).mode(0o755);
|
||||||
|
//let mut file = options.open(path)?;
|
||||||
|
//file.write_all(&buf)?;
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
@ -67,8 +67,12 @@ impl Vestal {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
imports.push((thunk, orig, import));
|
imports.push((thunk, orig, import));
|
||||||
if self.addr_to_import.contains_key(&call_via) {
|
if let Some(existing) = self.addr_to_import.get(&call_via) {
|
||||||
panic!("addr space overlap");
|
panic!("addr space overlap at 0x{call_via:x}: {}::{} vs {}::{}",
|
||||||
|
existing.0,
|
||||||
|
existing.1,
|
||||||
|
dep.to_string(),
|
||||||
|
name);
|
||||||
}
|
}
|
||||||
self.addr_to_import.insert(call_via, (dep.to_string(), name));
|
self.addr_to_import.insert(call_via, (dep.to_string(), name));
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +88,7 @@ impl Vestal {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn load_bang_data (&mut self, path: &Arc<PathBuf>) -> Usually<Arc<[u8]>> {
|
pub fn load_bang_data (&mut self, path: &Arc<PathBuf>) -> Usually<Arc<[u8]>> {
|
||||||
let (bang, data) = slice_shebang(read(path.as_path())?.as_slice());
|
let (bang, data) = crate::bang::slice_shebang(read(path.as_path())?.as_slice());
|
||||||
self.path_to_bang.insert(path.clone(), bang.clone());
|
self.path_to_bang.insert(path.clone(), bang.clone());
|
||||||
if bang.len() > 0 {
|
if bang.len() > 0 {
|
||||||
println!(" (bang {path:?} {:x})", bang.len())
|
println!(" (bang {path:?} {:x})", bang.len())
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
mod util;
|
mod util;
|
||||||
mod load;
|
mod load;
|
||||||
mod show;
|
mod show;
|
||||||
|
mod link;
|
||||||
|
mod bang;
|
||||||
pub(crate) use self::util::*;
|
pub(crate) use self::util::*;
|
||||||
use clap::{arg, command, value_parser, ArgAction, Command};
|
use clap::{arg, command, value_parser, ArgAction, Command};
|
||||||
fn main () -> Usually<()> {
|
fn main () -> Usually<()> {
|
||||||
|
|
@ -49,6 +51,24 @@ impl Vestal {
|
||||||
let len = dll_data.len();
|
let len = dll_data.len();
|
||||||
println!(" (bytes {len} 0x{len:x})");
|
println!(" (bytes {len} 0x{len:x})");
|
||||||
self.show_calls(dll_path, dll_path.as_ref() == path)?;
|
self.show_calls(dll_path, dll_path.as_ref() == path)?;
|
||||||
|
if dll_path.as_ref() == path {
|
||||||
|
println!("{:?}", dll_data.hex_dump());
|
||||||
|
let text_section = dll.get_section_by_name(".text")?;
|
||||||
|
println!("\n{:#?}", text_section);
|
||||||
|
let start = text_section.pointer_to_raw_data.0 as usize;
|
||||||
|
let size = text_section.size_of_raw_data as usize;
|
||||||
|
let vsize = text_section.virtual_size as usize;
|
||||||
|
println!("\n{:?}", &dll_data[start..start+size].hex_dump());
|
||||||
|
println!("\n{:?}", &dll_data[start..start+vsize].hex_dump());
|
||||||
|
let elf = crate::link::create_elf(&dll_data[start..start+vsize]);
|
||||||
|
println!("\n{:?}", &elf.hex_dump());
|
||||||
|
println!("\n{:?}", &elf.len());
|
||||||
|
let mut options = std::fs::OpenOptions::new();
|
||||||
|
options.write(true).create(true).mode(0o755);
|
||||||
|
let mut file = options.open("output")?;
|
||||||
|
file.write_all(&elf)?;
|
||||||
|
println!("\nDone.");
|
||||||
|
}
|
||||||
total += len;
|
total += len;
|
||||||
//println!("{:?}", dll_data.hex_dump());
|
//println!("{:?}", dll_data.hex_dump());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ impl Vestal {
|
||||||
self.addr_to_import.get(&call_target).unwrap_or(&unknown).1);
|
self.addr_to_import.get(&call_target).unwrap_or(&unknown).1);
|
||||||
let dependent = path.file_name().unwrap();
|
let dependent = path.file_name().unwrap();
|
||||||
if verbose {
|
if verbose {
|
||||||
println!(" ({BOLD}{external:30}{RESET} O=0x{:08x} {BOLD}R=0x{:08x}{RESET} {:25} {:40} 0x{:08x}",
|
println!(" ({BOLD}{external}{RESET}\n Offset(0x{:08x}) RVA(R=0x{:08x})\n {:25} {:40} 0x{:08x}",
|
||||||
offset,
|
offset,
|
||||||
offset_rva,
|
offset_rva,
|
||||||
opcodes.iter().map(|x|format!("{x:>02x}")).collect::<Vec<_>>().join(" "),
|
opcodes.iter().map(|x|format!("{x:>02x}")).collect::<Vec<_>>().join(" "),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
pub(crate) use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
pub(crate) use std::error::Error;
|
||||||
|
pub(crate) use std::fs::{read, canonicalize};
|
||||||
|
pub(crate) use std::io::Write;
|
||||||
|
pub(crate) use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
pub(crate) use std::path::{Path, PathBuf};
|
||||||
pub(crate) use std::pin::Pin;
|
pub(crate) use std::pin::Pin;
|
||||||
pub(crate) use std::sync::Arc;
|
pub(crate) use std::sync::Arc;
|
||||||
pub(crate) use std::error::Error;
|
|
||||||
pub(crate) use std::path::{Path, PathBuf};
|
|
||||||
pub(crate) use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
pub(crate) use std::fs::{read, canonicalize};
|
|
||||||
pub(crate) use itertools::izip;
|
pub(crate) use itertools::izip;
|
||||||
//pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations};
|
//pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations};
|
||||||
//pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}};
|
//pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}};
|
||||||
|
|
@ -16,16 +18,3 @@ pub(crate) use ::exe::{Buffer, PE, VecPE, PtrPE, types::*, headers::*};
|
||||||
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
|
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
pub const RESET: &str = "\u{001b}[0m";
|
pub const RESET: &str = "\u{001b}[0m";
|
||||||
pub const BOLD: &str = "\u{001b}[1m";
|
pub const BOLD: &str = "\u{001b}[1m";
|
||||||
/// You can manually patch DLLs by prepending
|
|
||||||
/// a `#!/usr/bin/env vestal` line to them.
|
|
||||||
pub fn slice_shebang (buffer: &[u8]) -> (Arc<[u8]>, Arc<[u8]>) {
|
|
||||||
if buffer.get(0) == Some(&b'#') && buffer.get(1) == Some(&b'!') {
|
|
||||||
if let Some((bang, data)) = buffer.split_once(|x|*x==0x0a) {
|
|
||||||
(bang.to_vec().into(), data.to_vec().into())
|
|
||||||
} else {
|
|
||||||
(buffer.to_vec().into(), vec![].into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(vec![].into(), buffer.to_vec().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue