Skip to content

Commit f14f5f2

Browse files
committed
Use patchelf to fix ELF files
1 parent 5af9b94 commit f14f5f2

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

src/dist/component/package.rs

+170
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ impl Package for DirectoryPackage {
113113
} else {
114114
builder.move_file(path.clone(), &src_path)?
115115
}
116+
nix_patchelf_if_needed(&target.prefix().path().join(path.clone()))
116117
}
117118
"dir" => {
118119
if self.copy {
@@ -135,6 +136,175 @@ impl Package for DirectoryPackage {
135136
}
136137
}
137138

139+
fn nix_wrap_lld(dest_lld_path: &Path) -> Result<()> {
140+
use std::fs;
141+
use std::io::Write;
142+
use std::os::unix::fs::PermissionsExt;
143+
144+
let path = dest_lld_path.parent().unwrap();
145+
let mut unwrapped_name = path.file_name().unwrap().to_string_lossy().to_string();
146+
unwrapped_name.push_str("-unwrapped");
147+
let unwrapped_dir = path.with_file_name(unwrapped_name);
148+
fs::create_dir(&unwrapped_dir).context("failed to create unwrapped directory")?;
149+
let mut unwrapped_lld = unwrapped_dir;
150+
unwrapped_lld.push(dest_lld_path.file_name().unwrap());
151+
fs::rename(dest_lld_path, &unwrapped_lld).context("failed to move file")?;
152+
let mut ld_wrapper_path = std::env::current_exe()?
153+
.parent()
154+
.ok_or(anyhow!("failed to get parent directory"))?
155+
.with_file_name("nix-support");
156+
let mut file = std::fs::File::create(dest_lld_path)?;
157+
ld_wrapper_path.push("ld-wrapper.sh");
158+
159+
let wrapped_script = format!(
160+
"#!/usr/bin/env bash
161+
set -eu -o pipefail +o posix
162+
shopt -s nullglob
163+
export PROG=\"{}\"
164+
\"{}\" $@",
165+
unwrapped_lld.to_string_lossy().to_string(),
166+
ld_wrapper_path.to_string_lossy().to_string(),
167+
);
168+
file.write_all(wrapped_script.as_bytes())?;
169+
let mut permissions = file.metadata()?.permissions();
170+
permissions.set_mode(0o755);
171+
file.set_permissions(permissions)?;
172+
Ok(())
173+
}
174+
175+
fn nix_patchelf_if_needed(dest_path: &Path) {
176+
use std::fs::File;
177+
use std::os::unix::fs::FileExt;
178+
179+
struct ELFReader<'a> {
180+
file: &'a mut File,
181+
is_32bit: bool,
182+
is_little_end: bool,
183+
}
184+
185+
impl<'a> ELFReader<'a> {
186+
const MAGIC_NUMBER: &'static [u8] = &[0x7F, 0x45, 0x4c, 0x46];
187+
const ET_EXEC: u16 = 0x2;
188+
const ET_DYN: u16 = 0x3;
189+
const PT_INTERP: u32 = 0x3;
190+
191+
fn new(file: &'a mut File) -> Option<Self> {
192+
let mut magic_number = [0; 4];
193+
file.read_exact(&mut magic_number).ok()?;
194+
if Self::MAGIC_NUMBER != magic_number {
195+
return None;
196+
}
197+
let mut ei_class = [0; 1];
198+
file.read_exact_at(&mut ei_class, 0x4).ok()?;
199+
let is_32bit = ei_class[0] == 1;
200+
let mut ei_data = [0; 1];
201+
file.read_exact_at(&mut ei_data, 0x5).ok()?;
202+
let is_little_end = ei_data[0] == 1;
203+
Some(Self {
204+
file,
205+
is_32bit,
206+
is_little_end,
207+
})
208+
}
209+
210+
fn is_exec_or_dyn(&self) -> bool {
211+
let e_type = self.read_u16_at(0x10);
212+
e_type == Self::ET_EXEC || e_type == Self::ET_DYN
213+
}
214+
215+
fn e_phoff(&self) -> u64 {
216+
if self.is_32bit {
217+
self.read_u32_at(0x1C) as u64
218+
} else {
219+
self.read_u64_at(0x20)
220+
}
221+
}
222+
223+
fn e_phentsize(&self) -> u64 {
224+
let offset = if self.is_32bit { 0x2A } else { 0x36 };
225+
self.read_u16_at(offset) as u64
226+
}
227+
228+
fn e_phnum(&self) -> u64 {
229+
let offset = if self.is_32bit { 0x2C } else { 0x38 };
230+
self.read_u16_at(offset) as u64
231+
}
232+
233+
fn has_interp(&self) -> bool {
234+
let e_phoff = self.e_phoff();
235+
let e_phentsize = self.e_phentsize();
236+
let e_phnum = self.e_phnum();
237+
for i in 0..e_phnum {
238+
let p_type = self.read_u32_at(e_phoff + i * e_phentsize);
239+
if p_type == Self::PT_INTERP {
240+
return true;
241+
}
242+
}
243+
false
244+
}
245+
246+
fn read_u16_at(&self, offset: u64) -> u16 {
247+
let mut data = [0; 2];
248+
self.file.read_exact_at(&mut data, offset).unwrap();
249+
if self.is_little_end {
250+
u16::from_le_bytes(data)
251+
} else {
252+
u16::from_be_bytes(data)
253+
}
254+
}
255+
256+
fn read_u32_at(&self, offset: u64) -> u32 {
257+
let mut data = [0; 4];
258+
self.file.read_exact_at(&mut data, offset).unwrap();
259+
if self.is_little_end {
260+
u32::from_le_bytes(data)
261+
} else {
262+
u32::from_be_bytes(data)
263+
}
264+
}
265+
266+
fn read_u64_at(&self, offset: u64) -> u64 {
267+
let mut data = [0; 8];
268+
self.file.read_exact_at(&mut data, offset).unwrap();
269+
if self.is_little_end {
270+
u64::from_le_bytes(data)
271+
} else {
272+
u64::from_be_bytes(data)
273+
}
274+
}
275+
}
276+
277+
let Some(mut dest_file) = File::open(dest_path).ok() else {
278+
return;
279+
};
280+
let Some(elf) = ELFReader::new(&mut dest_file) else {
281+
return;
282+
};
283+
if !elf.is_exec_or_dyn() {
284+
return;
285+
}
286+
let mut patch_command = std::process::Command::new("@patchelf@/bin/patchelf");
287+
if elf.has_interp() {
288+
patch_command
289+
.arg("--set-interpreter")
290+
.arg("@dynamicLinker@");
291+
}
292+
if Some(std::ffi::OsStr::new("rust-lld")) == dest_path.file_name() || !elf.has_interp() {
293+
patch_command.arg("--add-rpath").arg("@libPath@");
294+
}
295+
296+
debug!("patching {dest_path:?} using patchelf");
297+
if let Err(err) = patch_command.arg(dest_path).output() {
298+
warn!("failed to execute patchelf: {err:?}");
299+
}
300+
301+
if Some(std::ffi::OsStr::new("ld.lld")) == dest_path.file_name() {
302+
if let Err(err) = nix_wrap_lld(dest_path) {
303+
warn!("failed to wrap `ld.lld`: {err:?}");
304+
}
305+
}
306+
}
307+
138308
#[derive(Debug)]
139309
pub(crate) struct TarPackage<'a>(DirectoryPackage, temp::Dir<'a>);
140310

0 commit comments

Comments
 (0)