Skip to content

Commit 08dcc1c

Browse files
committed
Implement remaining required symbols and objects.
And break out code into dll_import_lib module.
1 parent 15f8053 commit 08dcc1c

File tree

7 files changed

+995
-708
lines changed

7 files changed

+995
-708
lines changed

src/archive.rs

Lines changed: 1 addition & 708 deletions
Large diffs are not rendered by default.

src/dll_import_lib/ar.rs

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//! https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#archive-library-file-format
2+
//!
3+
//! Windows .lib files are System-V (aka. GNU) flavored ar files with an additional MSVC-specific
4+
//! symbol lookup member after the standard one.
5+
//!
6+
//! An ar archive is the 8 bytes `b"!<arch>\n"` followed by a sequence of 60 byte member headers:
7+
//!
8+
//! ```plaintext
9+
//! 0: name: [u8; 16], // member name, terminated with "/". If it is longer than 15, then
10+
//! // use "/n" where "n" is a decimal for the offset in bytes into
11+
//! // the longnames ("//") member contents.
12+
//! 16: date: [u8; 12], // ASCII decimal seconds since UNIX epoch - always -1 for MSVC
13+
//! 28: uid: [u8; 6], // ASCII decimal user id. Always blank for MSVC
14+
//! 34: gid: [u8; 6], // ditto for group id.
15+
//! 40: mode: [u8; 8], // ASCII octal UNIX mode. 0 for MSVC
16+
//! 48: size: [u8; 10], // ASCII decimal data size.
17+
//! 58: end: b"`\n",
18+
//! ```
19+
//!
20+
//! then `size` bytes of member payload data. If payload is odd sized, it must be padded to an even
21+
//! offset with `\n`.
22+
//!
23+
//! Standard archives have an initial member with the raw name `/` containing a table with the
24+
//! offsets of the members containing exported symbols, with big-endian encoding:
25+
//!
26+
//! ```plaintext
27+
//! count: u32_be, // number of indexed symbols
28+
//! offsets: [u32_be, count], // file offsets to the header of the member that contains
29+
//! // that symbol.
30+
//! names: * // sequence of null terminated symbol names.
31+
//! ```
32+
//!
33+
//! MSVC lib archives then have an additional table member that also has the name `/`, and stores
34+
//! the same information. This uses little-endian encoding, separates the member offset table from
35+
//! the symbol table, and requires symbols to be sorted to allow binary search lookups.
36+
//!
37+
//! ```plaintext
38+
//! member_count: u32, // number of members
39+
//! member_offsets: [u32; member_count], // file offsets to each member header
40+
//! symbol_count: u32, // number of symbols
41+
//! symbol_member: [u16; symbol_count], // *1-based* index of the member that contains
42+
//! // each symbol
43+
//! symbol_names: * // sequence of null terminated symbol names in the same
44+
//! // order as symbol_member.
45+
//! ```
46+
//!
47+
//! Then the standard long names member (`//`), which stores just a sequence of null terminated
48+
//! strings indexed by members using the long name format `/n` as described above. This is not
49+
//! required for MSVC if there are no long names.
50+
//!
51+
//! Then content members follow.
52+
//!
53+
//! The member name doesn't seem to matter, including duplicates, for import libraries MSVC uses
54+
//! the dll name for every member.
55+
//!
56+
//! The short import object has the form:
57+
//!
58+
//! ```plaintext
59+
//! 0: header:
60+
//! 0: sig1: 0u16
61+
//! 2: sig2: 0xFFFFu16
62+
//! 4: version: u16, // normally 0
63+
//! 6: machine: u16, // IMAGE_MACHINE_* value, e.g. 0x8664 for AMD64
64+
//! 8: time_date_stamp: u32, // normally 0
65+
//! 12: size_of_data: u32, // size following the header
66+
//! 16: ordinal_or_hint: u16, // depending on flag
67+
//! 18: object_type: u2, // IMPORT_OBJECT_{CODE,DATA,CONST} = 0, 1, 2
68+
//! name_type: u3, // IMPORT_OBJECT_{ORDINAL,NAME,NAME_NO_PREFIX,NAME_UNDECORATE,NAME_EXPORTAS} = 0, 1, 2, 3, 4
69+
//! reserved: u11,
70+
//! 20: data: // size_of_data bytes
71+
//! name: * // import name; null terminated string
72+
//! dll_name: * // dll name; null terminated string
73+
//! ```
74+
75+
use std::io::Write;
76+
use std::ops::{Deref, DerefMut};
77+
78+
use super::string_table::StringTable;
79+
use super::DataWriter;
80+
81+
#[derive(Copy, Clone)]
82+
pub(crate) struct MemberName(pub [u8; 16]);
83+
84+
impl MemberName {
85+
pub(crate) const SYMBOL_TABLE: Self = MemberName(*b"/ ");
86+
pub(crate) const LONG_NAMES: Self = MemberName(*b"// ");
87+
}
88+
89+
pub(crate) struct Writer {
90+
data: DataWriter,
91+
long_names: Option<StringTable>,
92+
}
93+
94+
impl Writer {
95+
#[allow(clippy::new_without_default)] // Default should probably not write a signature?
96+
pub(crate) fn new() -> Self {
97+
let long_names = Some(StringTable::new());
98+
let mut data = DataWriter::new();
99+
data.write(b"!<arch>\n");
100+
Self { data, long_names }
101+
}
102+
103+
pub(crate) fn member_name(&mut self, name: &str) -> MemberName {
104+
let Some(ref mut long_buf) = self.long_names else {
105+
panic!("already wrote long names member");
106+
};
107+
108+
if name.len() < 16 {
109+
let mut buf = [b' '; 16];
110+
buf[..name.len()].copy_from_slice(name.as_bytes());
111+
buf[name.len()] = b'/';
112+
MemberName(buf)
113+
} else {
114+
let offset = long_buf.find_or_insert(name);
115+
let mut buf = [b' '; 16];
116+
write!(&mut buf[..], "/{offset}").expect("writing long name should not fail");
117+
MemberName(buf)
118+
}
119+
}
120+
121+
pub(crate) fn start_member(&mut self, name: MemberName) -> Member<'_> {
122+
Member::new(&mut self.data, name)
123+
}
124+
125+
pub(crate) fn write_long_names(&mut self) {
126+
let string_table = self.long_names.take().expect("already wrote long names member");
127+
let mut member = self.start_member(MemberName::LONG_NAMES);
128+
member.write(string_table.data());
129+
drop(member);
130+
}
131+
132+
pub(crate) fn into_data(self) -> Vec<u8> {
133+
self.data.into_data()
134+
}
135+
}
136+
137+
impl Deref for Writer {
138+
type Target = DataWriter;
139+
140+
fn deref(&self) -> &Self::Target {
141+
&self.data
142+
}
143+
}
144+
145+
impl DerefMut for Writer {
146+
fn deref_mut(&mut self) -> &mut Self::Target {
147+
&mut self.data
148+
}
149+
}
150+
151+
pub(crate) struct Member<'data> {
152+
pub(crate) data: &'data mut DataWriter,
153+
pub(crate) header_offset: usize,
154+
}
155+
156+
impl<'data> Member<'data> {
157+
const HEADER_SIZE: usize = std::mem::size_of::<object::archive::Header>();
158+
159+
fn new(data: &'data mut DataWriter, name: MemberName) -> Self {
160+
// fill the header MSVC defaults.
161+
let header_offset = data.write_pod(&object::archive::Header {
162+
name: name.0,
163+
date: *b"-1 ",
164+
uid: [b' '; 6],
165+
gid: [b' '; 6],
166+
mode: *b"0 ",
167+
size: [b' '; 10], // filled out in Drop
168+
terminator: object::archive::TERMINATOR,
169+
});
170+
171+
Self { data, header_offset }
172+
}
173+
174+
pub(crate) fn header_mut(&mut self) -> &mut object::archive::Header {
175+
self.data.get_pod_mut(self.header_offset)
176+
}
177+
178+
pub(crate) fn set_name(&mut self, name: MemberName) {
179+
self.header_mut().name = name.0;
180+
}
181+
182+
pub(crate) fn set_time_date_stamp(&mut self, value: i32) -> std::io::Result<()> {
183+
let header = self.header_mut();
184+
write!(&mut header.date[..], "{value:<12}")
185+
}
186+
187+
pub(crate) fn set_uid(&mut self, value: Option<u32>) -> std::io::Result<()> {
188+
let header = self.header_mut();
189+
if let Some(value) = value {
190+
write!(&mut header.uid[..], "{value:<6}")
191+
} else {
192+
header.uid.fill(b' ');
193+
Ok(())
194+
}
195+
}
196+
197+
pub(crate) fn set_gid(&mut self, value: Option<u32>) -> std::io::Result<()> {
198+
let header = self.header_mut();
199+
if let Some(value) = value {
200+
write!(&mut header.gid[..], "{value:<6}")
201+
} else {
202+
header.gid.fill(b' ');
203+
Ok(())
204+
}
205+
}
206+
207+
pub(crate) fn set_mode(&mut self, value: u16) -> std::io::Result<()> {
208+
write!(&mut self.header_mut().mode[..], "{value:o<8}")
209+
}
210+
}
211+
212+
impl Deref for Member<'_> {
213+
type Target = DataWriter;
214+
215+
fn deref(&self) -> &Self::Target {
216+
self.data
217+
}
218+
}
219+
220+
impl DerefMut for Member<'_> {
221+
fn deref_mut(&mut self) -> &mut Self::Target {
222+
self.data
223+
}
224+
}
225+
226+
impl<'a> Drop for Member<'a> {
227+
fn drop(&mut self) {
228+
let data_start = self.header_offset + Self::HEADER_SIZE;
229+
let data_size = self.data.len() - data_start;
230+
write!(&mut self.header_mut().size[..], "{data_size}")
231+
.expect("data size should always fit in 10 bytes");
232+
self.data.align(2, b'\n');
233+
}
234+
}

0 commit comments

Comments
 (0)