1 | #![allow (rustc::default_hash_types, rustc::potential_query_instability)] |
2 | |
3 | // Derived from code in LLVM, which is: |
4 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
5 | // See https://llvm.org/LICENSE.txt for license information. |
6 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
7 | |
8 | // Derived from: |
9 | // * https://github.com/llvm/llvm-project/blob/3d3ef9d073e1e27ea57480b371b7f5a9f5642ed2/llvm/include/llvm/Object/ArchiveWriter.h |
10 | // * https://github.com/llvm/llvm-project/blob/3d3ef9d073e1e27ea57480b371b7f5a9f5642ed2/llvm/lib/Object/ArchiveWriter.cpp |
11 | |
12 | use std::collections::HashMap; |
13 | use std::io::{self, Cursor, Seek, Write}; |
14 | |
15 | use object::{Object, ObjectSymbol}; |
16 | |
17 | use crate::alignment::*; |
18 | use crate::archive::*; |
19 | |
20 | pub struct NewArchiveMember<'a> { |
21 | pub buf: Box<dyn AsRef<[u8]> + 'a>, |
22 | pub get_symbols: fn(buf: &[u8], f: &mut dyn FnMut(&[u8]) -> io::Result<()>) -> io::Result<bool>, |
23 | pub member_name: String, |
24 | pub mtime: u64, |
25 | pub uid: u32, |
26 | pub gid: u32, |
27 | pub perms: u32, |
28 | } |
29 | |
30 | fn is_darwin(kind: ArchiveKind) -> bool { |
31 | matches!(kind, ArchiveKind::Darwin | ArchiveKind::Darwin64) |
32 | } |
33 | |
34 | fn is_aix_big_archive(kind: ArchiveKind) -> bool { |
35 | kind == ArchiveKind::AixBig |
36 | } |
37 | |
38 | fn is_bsd_like(kind: ArchiveKind) -> bool { |
39 | match kind { |
40 | ArchiveKind::Gnu | ArchiveKind::Gnu64 | ArchiveKind::AixBig => false, |
41 | ArchiveKind::Bsd | ArchiveKind::Darwin | ArchiveKind::Darwin64 => true, |
42 | ArchiveKind::Coff => panic!("not supported for writing" ), |
43 | } |
44 | } |
45 | |
46 | fn print_rest_of_member_header<W: Write>( |
47 | w: &mut W, |
48 | mtime: u64, |
49 | uid: u32, |
50 | gid: u32, |
51 | perms: u32, |
52 | size: u64, |
53 | ) -> io::Result<()> { |
54 | // The format has only 6 chars for uid and gid. Truncate if the provided |
55 | // values don't fit. |
56 | write!( |
57 | w, |
58 | " {:<12}{:<6}{:<6}{:<8o}{:<10}` \n" , |
59 | mtime, |
60 | uid % 1000000, |
61 | gid % 1000000, |
62 | perms, |
63 | size |
64 | ) |
65 | } |
66 | |
67 | fn print_gnu_small_member_header<W: Write>( |
68 | w: &mut W, |
69 | name: String, |
70 | mtime: u64, |
71 | uid: u32, |
72 | gid: u32, |
73 | perms: u32, |
74 | size: u64, |
75 | ) -> io::Result<()> { |
76 | write!(w, " {:<16}" , name + "/" )?; |
77 | print_rest_of_member_header(w, mtime, uid, gid, perms, size) |
78 | } |
79 | |
80 | fn print_bsd_member_header<W: Write>( |
81 | w: &mut W, |
82 | pos: u64, |
83 | name: &str, |
84 | mtime: u64, |
85 | uid: u32, |
86 | gid: u32, |
87 | perms: u32, |
88 | size: u64, |
89 | ) -> io::Result<()> { |
90 | let pos_after_header: u64 = pos + 60 + u64::try_from(name.len()).unwrap(); |
91 | // Pad so that even 64 bit object files are aligned. |
92 | let pad: u64 = offset_to_alignment(value:pos_after_header, alignment:8); |
93 | let name_with_padding: u64 = u64::try_from(name.len()).unwrap() + pad; |
94 | write!(w, "#1/ {:<13}" , name_with_padding)?; |
95 | print_rest_of_member_header(w, mtime, uid, gid, perms, size:name_with_padding + size)?; |
96 | write!(w, " {}" , name)?; |
97 | write!( |
98 | w, |
99 | " {nil:\0<pad$}" , |
100 | nil = "" , |
101 | pad = usize::try_from(pad).unwrap() |
102 | ) |
103 | } |
104 | |
105 | fn print_big_archive_member_header<W: Write>( |
106 | w: &mut W, |
107 | name: &str, |
108 | mtime: u64, |
109 | uid: u32, |
110 | gid: u32, |
111 | perms: u32, |
112 | size: u64, |
113 | prev_offset: u64, |
114 | next_offset: u64, |
115 | ) -> io::Result<()> { |
116 | write!( |
117 | w, |
118 | " {:<20}{:<20}{:<20}{:<12}{:<12}{:<12}{:<12o}{:<4}" , |
119 | size, |
120 | next_offset, |
121 | prev_offset, |
122 | mtime, |
123 | u64::from(uid) % 1000000000000u64, |
124 | u64::from(gid) % 1000000000000u64, |
125 | perms, |
126 | name.len(), |
127 | )?; |
128 | |
129 | if !name.is_empty() { |
130 | write!(w, " {}" , name)?; |
131 | |
132 | if name.len() % 2 != 0 { |
133 | write!(w, " \0" )?; |
134 | } |
135 | } |
136 | |
137 | write!(w, "` \n" )?; |
138 | |
139 | Ok(()) |
140 | } |
141 | |
142 | fn use_string_table(thin: bool, name: &str) -> bool { |
143 | thin || name.len() >= 16 || name.contains('/' ) |
144 | } |
145 | |
146 | fn is_64bit_kind(kind: ArchiveKind) -> bool { |
147 | match kind { |
148 | ArchiveKind::Gnu | ArchiveKind::Bsd | ArchiveKind::Darwin | ArchiveKind::Coff => false, |
149 | ArchiveKind::AixBig | ArchiveKind::Darwin64 | ArchiveKind::Gnu64 => true, |
150 | } |
151 | } |
152 | |
153 | fn print_member_header<'m, W: Write, T: Write + Seek>( |
154 | w: &mut W, |
155 | pos: u64, |
156 | string_table: &mut T, |
157 | member_names: &mut HashMap<&'m str, u64>, |
158 | kind: ArchiveKind, |
159 | thin: bool, |
160 | m: &'m NewArchiveMember<'m>, |
161 | mtime: u64, |
162 | size: u64, |
163 | ) -> io::Result<()> { |
164 | if is_bsd_like(kind) { |
165 | return print_bsd_member_header(w, pos, &m.member_name, mtime, m.uid, m.gid, m.perms, size); |
166 | } |
167 | |
168 | if !use_string_table(thin, &m.member_name) { |
169 | return print_gnu_small_member_header( |
170 | w, |
171 | m.member_name.clone(), |
172 | mtime, |
173 | m.uid, |
174 | m.gid, |
175 | m.perms, |
176 | size, |
177 | ); |
178 | } |
179 | |
180 | write!(w, "/" )?; |
181 | let name_pos; |
182 | if thin { |
183 | name_pos = string_table.stream_position()?; |
184 | write!(string_table, " {}/ \n" , m.member_name)?; |
185 | } else { |
186 | if let Some(&pos) = member_names.get(&*m.member_name) { |
187 | name_pos = pos; |
188 | } else { |
189 | name_pos = string_table.stream_position()?; |
190 | member_names.insert(&m.member_name, name_pos); |
191 | write!(string_table, " {}/ \n" , m.member_name)?; |
192 | } |
193 | } |
194 | write!(w, " {:<15}" , name_pos)?; |
195 | print_rest_of_member_header(w, mtime, m.uid, m.gid, m.perms, size) |
196 | } |
197 | |
198 | struct MemberData<'a> { |
199 | symbols: Vec<u64>, |
200 | header: Vec<u8>, |
201 | data: &'a [u8], |
202 | padding: &'static [u8], |
203 | } |
204 | |
205 | fn compute_string_table(names: &[u8]) -> MemberData<'_> { |
206 | let size: u64 = u64::try_from(names.len()).unwrap(); |
207 | let pad: u64 = offset_to_alignment(value:size, alignment:2); |
208 | let mut header: Vec = Vec::new(); |
209 | write!(header, " {:<48}" , "//" ).unwrap(); |
210 | write!(header, " {:<10}" , size + pad).unwrap(); |
211 | write!(header, "` \n" ).unwrap(); |
212 | MemberData { |
213 | symbols: vec![], |
214 | header, |
215 | data: names, |
216 | padding: if pad != 0 { b" \n" } else { b"" }, |
217 | } |
218 | } |
219 | |
220 | fn now(deterministic: bool) -> u64 { |
221 | if !deterministic { |
222 | todo!("non deterministic mode is not yet supported" ); // FIXME |
223 | } |
224 | 0 |
225 | } |
226 | |
227 | fn is_archive_symbol(sym: &object::read::Symbol<'_, '_>) -> bool { |
228 | // FIXME Use a better equivalent of LLVM's SymbolRef::SF_FormatSpecific |
229 | if sym.kind() == object::SymbolKind::Null |
230 | || sym.kind() == object::SymbolKind::File |
231 | || sym.kind() == object::SymbolKind::Section |
232 | { |
233 | return false; |
234 | } |
235 | if !sym.is_global() { |
236 | return false; |
237 | } |
238 | if sym.is_undefined() { |
239 | return false; |
240 | } |
241 | true |
242 | } |
243 | |
244 | fn print_n_bits<W: Write>(w: &mut W, kind: ArchiveKind, val: u64) -> io::Result<()> { |
245 | if is_64bit_kind(kind) { |
246 | w.write_all(&if is_bsd_like(kind) { |
247 | u64::to_le_bytes(self:val) |
248 | } else { |
249 | u64::to_be_bytes(self:val) |
250 | }) |
251 | } else { |
252 | w.write_all(&if is_bsd_like(kind) { |
253 | u32::to_le_bytes(self:u32::try_from(val).unwrap()) |
254 | } else { |
255 | u32::to_be_bytes(self:u32::try_from(val).unwrap()) |
256 | }) |
257 | } |
258 | } |
259 | |
260 | fn compute_symbol_table_size_and_pad( |
261 | kind: ArchiveKind, |
262 | num_syms: u64, |
263 | offset_size: u64, |
264 | string_table: &[u8], |
265 | ) -> (u64, u64) { |
266 | assert!( |
267 | offset_size == 4 || offset_size == 8, |
268 | "Unsupported offset_size" |
269 | ); |
270 | let mut size = offset_size; // Number of entries |
271 | if is_bsd_like(kind) { |
272 | size += num_syms * offset_size * 2; // Table |
273 | } else { |
274 | size += num_syms * offset_size; // Table |
275 | } |
276 | if is_bsd_like(kind) { |
277 | size += offset_size; // byte count; |
278 | } |
279 | size += u64::try_from(string_table.len()).unwrap(); |
280 | // ld64 expects the members to be 8-byte aligned for 64-bit content and at |
281 | // least 4-byte aligned for 32-bit content. Opt for the larger encoding |
282 | // uniformly. |
283 | // We do this for all bsd formats because it simplifies aligning members. |
284 | let pad = if is_aix_big_archive(kind) { |
285 | 0 |
286 | } else { |
287 | offset_to_alignment(size, if is_bsd_like(kind) { 8 } else { 2 }) |
288 | }; |
289 | size += pad; |
290 | (size, pad) |
291 | } |
292 | |
293 | fn write_symbol_table_header<W: Write + Seek>( |
294 | w: &mut W, |
295 | kind: ArchiveKind, |
296 | deterministic: bool, |
297 | size: u64, |
298 | prev_member_offset: u64, |
299 | ) -> io::Result<()> { |
300 | if is_bsd_like(kind) { |
301 | let name = if is_64bit_kind(kind) { |
302 | "__.SYMDEF_64" |
303 | } else { |
304 | "__.SYMDEF" |
305 | }; |
306 | let pos = w.stream_position()?; |
307 | print_bsd_member_header(w, pos, name, now(deterministic), 0, 0, 0, size) |
308 | } else if is_aix_big_archive(kind) { |
309 | print_big_archive_member_header( |
310 | w, |
311 | "" , |
312 | now(deterministic), |
313 | 0, |
314 | 0, |
315 | 0, |
316 | size, |
317 | prev_member_offset, |
318 | 0, |
319 | ) |
320 | } else { |
321 | let name = if is_64bit_kind(kind) { "/SYM64" } else { "" }; |
322 | print_gnu_small_member_header(w, name.to_string(), now(deterministic), 0, 0, 0, size) |
323 | } |
324 | } |
325 | |
326 | fn write_symbol_table<W: Write + Seek>( |
327 | w: &mut W, |
328 | kind: ArchiveKind, |
329 | deterministic: bool, |
330 | members: &[MemberData<'_>], |
331 | string_table: &[u8], |
332 | prev_member_offset: u64, |
333 | ) -> io::Result<()> { |
334 | // We don't write a symbol table on an archive with no members -- except on |
335 | // Darwin, where the linker will abort unless the archive has a symbol table. |
336 | if string_table.is_empty() && !is_darwin(kind) { |
337 | return Ok(()); |
338 | } |
339 | |
340 | let num_syms = u64::try_from(members.iter().map(|m| m.symbols.len()).sum::<usize>()).unwrap(); |
341 | |
342 | let offset_size = if is_64bit_kind(kind) { 8 } else { 4 }; |
343 | let (size, pad) = compute_symbol_table_size_and_pad(kind, num_syms, offset_size, string_table); |
344 | write_symbol_table_header(w, kind, deterministic, size, prev_member_offset)?; |
345 | |
346 | let mut pos = if is_aix_big_archive(kind) { |
347 | u64::try_from(std::mem::size_of::<big_archive::FixLenHdr>()).unwrap() |
348 | } else { |
349 | w.stream_position()? + size |
350 | }; |
351 | |
352 | if is_bsd_like(kind) { |
353 | print_n_bits(w, kind, num_syms * 2 * offset_size)?; |
354 | } else { |
355 | print_n_bits(w, kind, num_syms)?; |
356 | } |
357 | |
358 | for m in members { |
359 | for &string_offset in &m.symbols { |
360 | if is_bsd_like(kind) { |
361 | print_n_bits(w, kind, string_offset)?; |
362 | } |
363 | print_n_bits(w, kind, pos)?; // member offset |
364 | } |
365 | pos += u64::try_from(m.header.len() + m.data.len() + m.padding.len()).unwrap(); |
366 | } |
367 | |
368 | if is_bsd_like(kind) { |
369 | // byte count of the string table |
370 | print_n_bits(w, kind, u64::try_from(string_table.len()).unwrap())?; |
371 | } |
372 | |
373 | w.write_all(string_table)?; |
374 | |
375 | write!( |
376 | w, |
377 | " {nil:\0<pad$}" , |
378 | nil = "" , |
379 | pad = usize::try_from(pad).unwrap() |
380 | ) |
381 | } |
382 | |
383 | pub fn get_native_object_symbols( |
384 | buf: &[u8], |
385 | f: &mut dyn FnMut(&[u8]) -> io::Result<()>, |
386 | ) -> io::Result<bool> { |
387 | // FIXME match what LLVM does |
388 | |
389 | match object::File::parse(data:buf) { |
390 | Ok(file: File<'_>) => { |
391 | for sym: Symbol<'_, '_> in file.symbols() { |
392 | if !is_archive_symbol(&sym) { |
393 | continue; |
394 | } |
395 | f(sym.name_bytes().expect(msg:"FIXME" ))?; |
396 | } |
397 | Ok(true) |
398 | } |
399 | Err(_) => Ok(false), |
400 | } |
401 | } |
402 | |
403 | // NOTE: LLVM calls this getSymbols and has the get_native_symbols function inlined |
404 | fn write_symbols( |
405 | buf: &[u8], |
406 | get_symbols: fn(buf: &[u8], f: &mut dyn FnMut(&[u8]) -> io::Result<()>) -> io::Result<bool>, |
407 | sym_names: &mut Cursor<Vec<u8>>, |
408 | has_object: &mut bool, |
409 | ) -> io::Result<Vec<u64>> { |
410 | let mut ret: Vec = vec![]; |
411 | // We only set has_object if get_symbols determines it's looking at an |
412 | // object file. This is because if we're creating an rlib, the archive will |
413 | // always end in lib.rmeta, and cause has_object to always become false. |
414 | if get_symbols(buf, &mut |sym: &[u8]| { |
415 | ret.push(sym_names.stream_position()?); |
416 | sym_names.write_all(buf:sym)?; |
417 | sym_names.write_all(&[0])?; |
418 | Ok(()) |
419 | })? { |
420 | *has_object = true; |
421 | } |
422 | Ok(ret) |
423 | } |
424 | |
425 | fn compute_member_data<'a, S: Write + Seek>( |
426 | string_table: &mut S, |
427 | sym_names: &mut Cursor<Vec<u8>>, |
428 | kind: ArchiveKind, |
429 | thin: bool, |
430 | deterministic: bool, |
431 | need_symbols: bool, |
432 | new_members: &'a [NewArchiveMember<'a>], |
433 | ) -> io::Result<Vec<MemberData<'a>>> { |
434 | const PADDING_DATA: &[u8; 8] = &[b' \n' ; 8]; |
435 | |
436 | // This ignores the symbol table, but we only need the value mod 8 and the |
437 | // symbol table is aligned to be a multiple of 8 bytes |
438 | let mut pos = if is_aix_big_archive(kind) { |
439 | u64::try_from(std::mem::size_of::<big_archive::FixLenHdr>()).unwrap() |
440 | } else { |
441 | 0 |
442 | }; |
443 | |
444 | let mut ret = vec![]; |
445 | let mut has_object = false; |
446 | |
447 | // Deduplicate long member names in the string table and reuse earlier name |
448 | // offsets. This especially saves space for COFF Import libraries where all |
449 | // members have the same name. |
450 | let mut member_names = HashMap::<&str, u64>::new(); |
451 | |
452 | // UniqueTimestamps is a special case to improve debugging on Darwin: |
453 | // |
454 | // The Darwin linker does not link debug info into the final |
455 | // binary. Instead, it emits entries of type N_OSO in in the output |
456 | // binary's symbol table, containing references to the linked-in |
457 | // object files. Using that reference, the debugger can read the |
458 | // debug data directly from the object files. Alternatively, an |
459 | // invocation of 'dsymutil' will link the debug data from the object |
460 | // files into a dSYM bundle, which can be loaded by the debugger, |
461 | // instead of the object files. |
462 | // |
463 | // For an object file, the N_OSO entries contain the absolute path |
464 | // path to the file, and the file's timestamp. For an object |
465 | // included in an archive, the path is formatted like |
466 | // "/absolute/path/to/archive.a(member.o)", and the timestamp is the |
467 | // archive member's timestamp, rather than the archive's timestamp. |
468 | // |
469 | // However, this doesn't always uniquely identify an object within |
470 | // an archive -- an archive file can have multiple entries with the |
471 | // same filename. (This will happen commonly if the original object |
472 | // files started in different directories.) The only way they get |
473 | // distinguished, then, is via the timestamp. But this process is |
474 | // unable to find the correct object file in the archive when there |
475 | // are two files of the same name and timestamp. |
476 | // |
477 | // Additionally, timestamp==0 is treated specially, and causes the |
478 | // timestamp to be ignored as a match criteria. |
479 | // |
480 | // That will "usually" work out okay when creating an archive not in |
481 | // deterministic timestamp mode, because the objects will probably |
482 | // have been created at different timestamps. |
483 | // |
484 | // To ameliorate this problem, in deterministic archive mode (which |
485 | // is the default), on Darwin we will emit a unique non-zero |
486 | // timestamp for each entry with a duplicated name. This is still |
487 | // deterministic: the only thing affecting that timestamp is the |
488 | // order of the files in the resultant archive. |
489 | // |
490 | // See also the functions that handle the lookup: |
491 | // in lldb: ObjectContainerBSDArchive::Archive::FindObject() |
492 | // in llvm/tools/dsymutil: BinaryHolder::GetArchiveMemberBuffers(). |
493 | let unique_timestamps = deterministic && is_darwin(kind); |
494 | let mut filename_count = HashMap::new(); |
495 | if unique_timestamps { |
496 | for m in new_members { |
497 | *filename_count.entry(&*m.member_name).or_insert(0) += 1; |
498 | } |
499 | for (_name, count) in filename_count.iter_mut() { |
500 | if *count > 1 { |
501 | *count = 1; |
502 | } |
503 | } |
504 | } |
505 | |
506 | // The big archive format needs to know the offset of the previous member |
507 | // header. |
508 | let mut prev_offset = 0; |
509 | for m in new_members { |
510 | let mut header = Vec::new(); |
511 | |
512 | let data: &[u8] = if thin { &[][..] } else { (*m.buf).as_ref() }; |
513 | |
514 | // ld64 expects the members to be 8-byte aligned for 64-bit content and at |
515 | // least 4-byte aligned for 32-bit content. Opt for the larger encoding |
516 | // uniformly. This matches the behaviour with cctools and ensures that ld64 |
517 | // is happy with archives that we generate. |
518 | let member_padding = if is_darwin(kind) { |
519 | offset_to_alignment(u64::try_from(data.len()).unwrap(), 8) |
520 | } else { |
521 | 0 |
522 | }; |
523 | let tail_padding = |
524 | offset_to_alignment(u64::try_from(data.len()).unwrap() + member_padding, 2); |
525 | let padding = &PADDING_DATA[..usize::try_from(member_padding + tail_padding).unwrap()]; |
526 | |
527 | let mtime = if unique_timestamps { |
528 | // Increment timestamp for each file of a given name. |
529 | *filename_count.get_mut(&*m.member_name).unwrap() += 1; |
530 | filename_count[&*m.member_name] - 1 |
531 | } else { |
532 | m.mtime |
533 | }; |
534 | |
535 | let size = u64::try_from(data.len()).unwrap() + member_padding; |
536 | if size > MAX_MEMBER_SIZE { |
537 | return Err(io::Error::new( |
538 | io::ErrorKind::Other, |
539 | format!("Archive member {} is too big" , m.member_name), |
540 | )); |
541 | } |
542 | |
543 | if is_aix_big_archive(kind) { |
544 | let next_offset = pos |
545 | + u64::try_from(std::mem::size_of::<big_archive::BigArMemHdrType>()).unwrap() |
546 | + align_to(u64::try_from(m.member_name.len()).unwrap(), 2) |
547 | + align_to(size, 2); |
548 | print_big_archive_member_header( |
549 | &mut header, |
550 | &m.member_name, |
551 | mtime, |
552 | m.uid, |
553 | m.gid, |
554 | m.perms, |
555 | size, |
556 | prev_offset, |
557 | next_offset, |
558 | )?; |
559 | prev_offset = pos; |
560 | } else { |
561 | print_member_header( |
562 | &mut header, |
563 | pos, |
564 | string_table, |
565 | &mut member_names, |
566 | kind, |
567 | thin, |
568 | m, |
569 | mtime, |
570 | size, |
571 | )?; |
572 | } |
573 | |
574 | let symbols = if need_symbols { |
575 | write_symbols(data, m.get_symbols, sym_names, &mut has_object)? |
576 | } else { |
577 | vec![] |
578 | }; |
579 | |
580 | pos += u64::try_from(header.len() + data.len() + padding.len()).unwrap(); |
581 | ret.push(MemberData { |
582 | symbols, |
583 | header, |
584 | data, |
585 | padding, |
586 | }) |
587 | } |
588 | |
589 | // If there are no symbols, emit an empty symbol table, to satisfy Solaris |
590 | // tools, older versions of which expect a symbol table in a non-empty |
591 | // archive, regardless of whether there are any symbols in it. |
592 | if has_object && sym_names.stream_position()? == 0 { |
593 | write!(sym_names, " \0\0\0" )?; |
594 | } |
595 | |
596 | Ok(ret) |
597 | } |
598 | |
599 | pub fn write_archive_to_stream<W: Write + Seek>( |
600 | w: &mut W, |
601 | new_members: &[NewArchiveMember<'_>], |
602 | write_symtab: bool, |
603 | mut kind: ArchiveKind, |
604 | deterministic: bool, |
605 | thin: bool, |
606 | ) -> io::Result<()> { |
607 | assert!( |
608 | !thin || !is_bsd_like(kind), |
609 | "Only the gnu format has a thin mode" |
610 | ); |
611 | |
612 | let mut sym_names = Cursor::new(Vec::new()); |
613 | let mut string_table = Cursor::new(Vec::new()); |
614 | |
615 | let mut data = compute_member_data( |
616 | &mut string_table, |
617 | &mut sym_names, |
618 | kind, |
619 | thin, |
620 | deterministic, |
621 | write_symtab, |
622 | new_members, |
623 | )?; |
624 | |
625 | let sym_names = sym_names.into_inner(); |
626 | |
627 | let string_table = string_table.into_inner(); |
628 | if !string_table.is_empty() && !is_aix_big_archive(kind) { |
629 | data.insert(0, compute_string_table(&string_table)); |
630 | } |
631 | |
632 | // We would like to detect if we need to switch to a 64-bit symbol table. |
633 | let mut last_member_end_offset = if is_aix_big_archive(kind) { |
634 | u64::try_from(std::mem::size_of::<big_archive::FixLenHdr>()).unwrap() |
635 | } else { |
636 | 8 |
637 | }; |
638 | let mut last_member_header_offset = last_member_end_offset; |
639 | let mut num_syms = 0; |
640 | for m in &data { |
641 | // Record the start of the member's offset |
642 | last_member_header_offset = last_member_end_offset; |
643 | // Account for the size of each part associated with the member. |
644 | last_member_end_offset += |
645 | u64::try_from(m.header.len() + m.data.len() + m.padding.len()).unwrap(); |
646 | num_syms += u64::try_from(m.symbols.len()).unwrap(); |
647 | } |
648 | |
649 | // The symbol table is put at the end of the big archive file. The symbol |
650 | // table is at the start of the archive file for other archive formats. |
651 | if write_symtab && !is_aix_big_archive(kind) { |
652 | // We assume 32-bit offsets to see if 32-bit symbols are possible or not. |
653 | let (symtab_size, _pad) = compute_symbol_table_size_and_pad(kind, num_syms, 4, &sym_names); |
654 | last_member_header_offset += { |
655 | // FIXME avoid allocating memory here |
656 | let mut tmp = Cursor::new(vec![]); |
657 | write_symbol_table_header(&mut tmp, kind, deterministic, symtab_size, 0).unwrap(); |
658 | u64::try_from(tmp.into_inner().len()).unwrap() |
659 | } + symtab_size; |
660 | |
661 | // The SYM64 format is used when an archive's member offsets are larger than |
662 | // 32-bits can hold. The need for this shift in format is detected by |
663 | // writeArchive. To test this we need to generate a file with a member that |
664 | // has an offset larger than 32-bits but this demands a very slow test. To |
665 | // speed the test up we use this environment variable to pretend like the |
666 | // cutoff happens before 32-bits and instead happens at some much smaller |
667 | // value. |
668 | // FIXME allow lowering the threshold for tests |
669 | const SYM64_THRESHOLD: u64 = 1 << 32; |
670 | |
671 | // If LastMemberHeaderOffset isn't going to fit in a 32-bit varible we need |
672 | // to switch to 64-bit. Note that the file can be larger than 4GB as long as |
673 | // the last member starts before the 4GB offset. |
674 | if last_member_header_offset >= SYM64_THRESHOLD { |
675 | if kind == ArchiveKind::Darwin { |
676 | kind = ArchiveKind::Darwin64; |
677 | } else { |
678 | kind = ArchiveKind::Gnu64; |
679 | } |
680 | } |
681 | } |
682 | |
683 | if thin { |
684 | write!(w, "!<thin> \n" )?; |
685 | } else if is_aix_big_archive(kind) { |
686 | write!(w, "<bigaf> \n" )?; |
687 | } else { |
688 | write!(w, "!<arch> \n" )?; |
689 | } |
690 | |
691 | if !is_aix_big_archive(kind) { |
692 | if write_symtab { |
693 | write_symbol_table(w, kind, deterministic, &data, &sym_names, 0)?; |
694 | } |
695 | |
696 | for m in data { |
697 | w.write_all(&m.header)?; |
698 | w.write_all(m.data)?; |
699 | w.write_all(m.padding)?; |
700 | } |
701 | } else { |
702 | // For the big archive (AIX) format, compute a table of member names and |
703 | // offsets, used in the member table. |
704 | let mut member_table_name_str_tbl_size = 0; |
705 | let mut member_offsets = vec![]; |
706 | let mut member_names = vec![]; |
707 | |
708 | // Loop across object to find offset and names. |
709 | let mut member_end_offset = |
710 | u64::try_from(std::mem::size_of::<big_archive::FixLenHdr>()).unwrap(); |
711 | for i in 0..new_members.len() { |
712 | let member = &new_members[i]; |
713 | member_table_name_str_tbl_size += member.member_name.len() + 1; |
714 | member_offsets.push(member_end_offset); |
715 | member_names.push(&member.member_name); |
716 | // File member name ended with "`\n". The length is included in |
717 | // BigArMemHdrType. |
718 | member_end_offset += u64::try_from(std::mem::size_of::<big_archive::BigArMemHdrType>()) |
719 | .unwrap() |
720 | + align_to(u64::try_from(data[i].data.len()).unwrap(), 2) |
721 | + align_to(u64::try_from(member.member_name.len()).unwrap(), 2); |
722 | } |
723 | |
724 | // AIX member table size. |
725 | let member_table_size = |
726 | u64::try_from(20 + 20 * member_offsets.len() + member_table_name_str_tbl_size).unwrap(); |
727 | |
728 | let global_symbol_offset = if write_symtab && num_syms > 0 { |
729 | last_member_end_offset |
730 | + align_to( |
731 | u64::try_from(std::mem::size_of::<big_archive::BigArMemHdrType>()).unwrap() |
732 | + member_table_size, |
733 | 2, |
734 | ) |
735 | } else { |
736 | 0 |
737 | }; |
738 | |
739 | // Fixed Sized Header. |
740 | // Offset to member table |
741 | write!( |
742 | w, |
743 | " {:<20}" , |
744 | if !new_members.is_empty() { |
745 | last_member_end_offset |
746 | } else { |
747 | 0 |
748 | } |
749 | )?; |
750 | // If there are no file members in the archive, there will be no global |
751 | // symbol table. |
752 | write!( |
753 | w, |
754 | " {:<20}" , |
755 | if !new_members.is_empty() { |
756 | global_symbol_offset |
757 | } else { |
758 | 0 |
759 | } |
760 | )?; |
761 | // Offset to 64 bits global symbol table - Not supported yet |
762 | write!(w, " {:<20}" , 0)?; |
763 | // Offset to first archive member |
764 | write!( |
765 | w, |
766 | " {:<20}" , |
767 | if !new_members.is_empty() { |
768 | u64::try_from(std::mem::size_of::<big_archive::FixLenHdr>()).unwrap() |
769 | } else { |
770 | 0 |
771 | } |
772 | )?; |
773 | // Offset to last archive member |
774 | write!( |
775 | w, |
776 | " {:<20}" , |
777 | if !new_members.is_empty() { |
778 | last_member_header_offset |
779 | } else { |
780 | 0 |
781 | } |
782 | )?; |
783 | // Offset to first member of free list - Not supported yet |
784 | write!(w, " {:<20}" , 0)?; |
785 | |
786 | for m in &data { |
787 | w.write_all(&m.header)?; |
788 | w.write_all(m.data)?; |
789 | if m.data.len() % 2 != 0 { |
790 | w.write_all(&[0])?; |
791 | } |
792 | } |
793 | |
794 | if !new_members.is_empty() { |
795 | // Member table. |
796 | print_big_archive_member_header( |
797 | w, |
798 | "" , |
799 | 0, |
800 | 0, |
801 | 0, |
802 | 0, |
803 | member_table_size, |
804 | last_member_header_offset, |
805 | global_symbol_offset, |
806 | )?; |
807 | write!(w, " {:<20}" , member_offsets.len())?; // Number of members |
808 | for member_offset in member_offsets { |
809 | write!(w, " {:<20}" , member_offset)?; |
810 | } |
811 | for member_name in member_names { |
812 | w.write_all(member_name.as_bytes())?; |
813 | w.write_all(&[0])?; |
814 | } |
815 | |
816 | if member_table_name_str_tbl_size % 2 != 0 { |
817 | // Name table must be tail padded to an even number of |
818 | // bytes. |
819 | w.write_all(&[0])?; |
820 | } |
821 | |
822 | if write_symtab && num_syms > 0 { |
823 | write_symbol_table( |
824 | w, |
825 | kind, |
826 | deterministic, |
827 | &data, |
828 | &sym_names, |
829 | last_member_end_offset, |
830 | )?; |
831 | } |
832 | } |
833 | } |
834 | |
835 | w.flush() |
836 | } |
837 | |