Skip to content

Commit 136dc9d

Browse files
committed
WIP write out windows import library
1 parent 48ca2d9 commit 136dc9d

File tree

1 file changed

+283
-4
lines changed

1 file changed

+283
-4
lines changed

src/archive.rs

Lines changed: 283 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fs;
12
use std::path::{Path, PathBuf};
23

34
use rustc_codegen_ssa::back::archive::{
@@ -15,11 +16,289 @@ impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder {
1516
fn create_dll_import_lib(
1617
&self,
1718
_sess: &Session,
18-
_lib_name: &str,
19-
_dll_imports: &[rustc_session::cstore::DllImport],
20-
_tmpdir: &Path,
19+
lib_name: &str,
20+
dll_imports: &[rustc_session::cstore::DllImport],
21+
tmpdir: &Path,
2122
_is_direct_dependency: bool,
2223
) -> PathBuf {
23-
unimplemented!("creating dll imports is not yet supported");
24+
let mut import_names = Vec::new();
25+
for dll_import in dll_imports {
26+
import_names.push(dll_import.name.as_str());
27+
}
28+
let lib_path = tmpdir.join(format!("{}.lib", lib_name));
29+
// todo: emit session error instead of expects
30+
fs::write(&lib_path, windows_import_lib::generate(lib_name, &import_names))
31+
.expect("failed to write import library");
32+
33+
lib_path
34+
}
35+
}
36+
37+
// todo: pull out to a proper location. Really should be in `object` crate!
38+
// todo: support ordinals
39+
// todo: support name types (e.g. verbatim+)
40+
// todo: support long member names
41+
// todo: support windows-gnu flavor?
42+
// todo: provide machine
43+
// todo: remove any panics, nice errors
44+
mod windows_import_lib {
45+
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#archive-library-file-format
46+
//
47+
// Windows .lib files are System-V (aka. GUN) flavored ar files with a couple of extra lookup
48+
// members.
49+
//
50+
// An archive is the 8 bytes b"!<arch>\n"
51+
// followed by a sequence of 60 byte member headers:
52+
// 0: name: [u8; 16], // member name, terminated with "/". If it is longer than 15, then
53+
// // use "/n" where "n" is a decimal for the offset in bytes into
54+
// // the longnames ("//") member contents.
55+
// 16: date: [u8; 12], // ASCII decimal seconds since UNIX epoch - always -1 for MSVC
56+
// 28: uid: [u8; 6], // ASCII decimal user id. Always blank for MSVC
57+
// 34: gid: [u8; 6], // ditto for group id.
58+
// 40: mode: [u8; 8], // ASCII octal UNIX mode. 0 for MSVC
59+
// 48: size: [u8; 10], // ASCII decimal data size.
60+
// 58: end: b"`\n",
61+
// then size bytes of payload. If payload is odd sized, pad
62+
// to an even offset with \n.
63+
//
64+
// You must store two extra members at the start, a legacy member lookup table member
65+
// and the current member lookup and symbol table, both with empty ("/") names.
66+
//
67+
// The legacy table member has the name "/" with following contents, using big-endian numbers:
68+
// count: u32, // number of indexed symbols
69+
// offsets: [u32, count], // file offsets to the header of the member that contains
70+
// // that symbol.
71+
// names: * // sequence of null terminated symbol names.
72+
//
73+
// The current table member also has the name "/", and has the following contents, using
74+
// little-endian numbers:
75+
// member_count: u32, // number of members
76+
// member_offsets: [u32; member_count], // file offsets to each member header
77+
// symbol_count: u32, // number of symbols
78+
// symbol_member: [u16; symbol_count], // *1-based* index of the member that contains
79+
// // each symbol
80+
// symbol_names: * // sequence of null terminated symbol names
81+
//
82+
// Then the long names member ("//") as with regular GNU ar files, just a sequence of
83+
// null terminated strings indexed by members using the long name format "/n" as described
84+
// above.
85+
//
86+
// Then regular members follow.
87+
//
88+
// This library emits only import libraries, that is, libraries with a short import object
89+
// describing an import from a dll. That means each member contains exactly one symbol. The member
90+
// name doesn't seem to matter, including duplicates, we use the dll name since that's what's in the
91+
// files generated by MSVC tools.
92+
//
93+
// The short import object has the form:
94+
// header:
95+
// sig1: 0u16
96+
// sig2: 0xFFFFu16
97+
// version: u16, // normally 0
98+
// machine: u16, // IMAGE_MACHINE_* value, e.g. 0x8664 for AMD64
99+
// time_date_stamp: u32, // normally 0
100+
// size_of_data: u32, // size following the header
101+
// ordinal_or_hint: u16, // depending on flag
102+
// object_type: u2, // IMPORT_OBJECT_{CODE,DATA,CONST} = 0, 1, 2
103+
// name_type: u3, // IMPORT_OBJECT_{ORDINAL,NAME,NAME_NO_PREFIX,NAME_UNDECORATE,NAME_EXPORTAS} = 0, 1, 2, 3, 4
104+
// reserved: u11,
105+
// data: // size_of_data bytes
106+
// name: * // import name; null terminated string
107+
// dll_name: * // dll name; null terminated string
108+
pub fn generate(dll_name: &str, import_names: &[&str]) -> Vec<u8> {
109+
assert!(dll_name.len() < 16, "long member names not supported yet");
110+
assert!(import_names.len() <= 0xFFFF, "too many import names");
111+
// number of symbols, and members containing symbols for symbol lookup members
112+
let symbol_count = import_names.len();
113+
114+
let mut writer = Writer::new();
115+
116+
// legacy symbol directory
117+
let mut legacy_symbol_directory = writer.start_member_raw();
118+
legacy_symbol_directory.set_raw_name(b"/");
119+
legacy_symbol_directory.write_u32_be(symbol_count as u32);
120+
// reserve space for offsets.
121+
let legacy_member_table_offset = legacy_symbol_directory.reserve_bytes(symbol_count * 4);
122+
// string table
123+
for name in import_names {
124+
legacy_symbol_directory.write_c_str(name);
125+
}
126+
// done with legacy symbol directory
127+
drop(legacy_symbol_directory);
128+
129+
// current symbol directory
130+
let mut current_symbol_directory = writer.start_member_raw();
131+
current_symbol_directory.set_raw_name(b"/");
132+
// member count: same as symbol count for import library
133+
current_symbol_directory.write_u32_le(symbol_count as u32);
134+
// reserve space for member offsets
135+
let current_member_table_offset = current_symbol_directory.reserve_bytes(symbol_count * 4);
136+
// symbol count
137+
current_symbol_directory.write_u32_le(symbol_count as u32);
138+
// we assume symbol members are already in order
139+
for index in 0..import_names.len() as u16 {
140+
current_symbol_directory.write_u16_le(1 + index);
141+
}
142+
// string table again (could just copy from legacy string table above?)
143+
for name in import_names {
144+
current_symbol_directory.write_c_str(name);
145+
}
146+
// done with current symbol directory
147+
drop(current_symbol_directory);
148+
149+
// long names member not supported yet
150+
151+
// import members
152+
for (index, name) in import_names.iter().enumerate() {
153+
let mut member = writer.start_member(dll_name);
154+
// update member offsets
155+
let member_offset = member.header_offset as u32;
156+
member.data[legacy_member_table_offset + index * 4..][..4]
157+
.copy_from_slice(&member_offset.to_be_bytes());
158+
member.data[current_member_table_offset + index * 4..][..4]
159+
.copy_from_slice(&member_offset.to_le_bytes());
160+
// write import object:
161+
// signature
162+
member.write_u16_le(0);
163+
member.write_u16_le(0xFFFF);
164+
// version
165+
member.write_u16_le(0);
166+
// machine = AMD64
167+
member.write_u16_le(0x8664);
168+
// time_date_stamp
169+
member.write_u32_le(0);
170+
// size_of_data
171+
member.write_u32_le((dll_name.len() + 1 + name.len() + 1) as u32);
172+
// ordinal_or_hint
173+
member.write_u16_le(0);
174+
// object_type | name_type = IMPORT_OBJECT_CODE | IMPORT_OBJECT_NAME
175+
member.write_u16_le(1 << 2 | 0);
176+
// data:
177+
// name
178+
member.write_c_str(name);
179+
// dll_name
180+
member.write_c_str(dll_name);
181+
182+
drop(member);
183+
}
184+
185+
writer.data
186+
}
187+
188+
struct Writer {
189+
data: Vec<u8>,
190+
}
191+
192+
impl Writer {
193+
fn new() -> Self {
194+
Self { data: Vec::from(*b"!<arch>\n") }
195+
}
196+
197+
fn start_member_raw(&mut self) -> Member<'_> {
198+
let header_offset = self.data.len();
199+
// fill the header with blanks...
200+
self.data.resize(header_offset + Member::HEADER_SIZE - 2, b' ');
201+
// except for end marker
202+
self.data.extend_from_slice(b"`\n");
203+
204+
let mut member = Member::new(&mut self.data, header_offset);
205+
// init date, mode to default values as produced by MSVC tools
206+
member.set_time_date_stamp(-1);
207+
member.set_mode(0);
208+
member
209+
}
210+
211+
fn start_member(&mut self, name: &str) -> Member<'_> {
212+
let mut member = self.start_member_raw();
213+
member.set_name(name);
214+
member
215+
}
216+
}
217+
218+
struct Member<'a> {
219+
data: &'a mut Vec<u8>,
220+
header_offset: usize,
221+
}
222+
223+
impl<'a> Member<'a> {
224+
const HEADER_SIZE: usize = 60;
225+
226+
fn new(data: &'a mut Vec<u8>, header_offset: usize) -> Self {
227+
Self { data, header_offset }
228+
}
229+
230+
fn header_slice(&mut self, offset: usize, len: usize) -> &mut [u8] {
231+
&mut self.data[self.header_offset + offset..][..len]
232+
}
233+
234+
fn set_name(&mut self, name: &str) {
235+
assert!(name.len() < 16, "long member names not supported yet");
236+
self.set_raw_name(name.as_bytes());
237+
self.data[self.header_offset + name.len()] = b'/';
238+
}
239+
240+
fn set_raw_name(&mut self, raw_name: &[u8]) {
241+
assert!(raw_name.len() <= 16, "raw name must be <= 16 bytes");
242+
self.header_slice(0, raw_name.len()).copy_from_slice(raw_name);
243+
}
244+
245+
fn set_time_date_stamp(&mut self, value: i32) {
246+
self.set_decimal_field(16, 12, value);
247+
}
248+
249+
fn set_uid(&mut self, value: i32) {
250+
self.set_decimal_field(28, 6, value);
251+
}
252+
253+
fn set_gid(&mut self, value: i32) {
254+
self.set_decimal_field(34, 6, value);
255+
}
256+
257+
fn set_mode(&mut self, value: i32) {
258+
use std::io::Write;
259+
write!(std::io::Cursor::new(self.header_slice(40, 8)), "{value:o}")
260+
.expect("value too large");
261+
}
262+
263+
fn set_decimal_field(&mut self, offset: usize, size: usize, value: i32) {
264+
use std::io::Write;
265+
write!(std::io::Cursor::new(self.header_slice(offset, size)), "{value}")
266+
.expect("value too large");
267+
}
268+
269+
fn write_c_str(&mut self, data: &str) {
270+
self.data.extend_from_slice(data.as_bytes());
271+
self.data.push(0);
272+
}
273+
274+
fn write_u16_le(&mut self, data: u16) {
275+
self.data.extend_from_slice(&data.to_le_bytes());
276+
}
277+
278+
fn write_u32_be(&mut self, data: u32) {
279+
self.data.extend_from_slice(&data.to_be_bytes());
280+
}
281+
282+
fn write_u32_le(&mut self, data: u32) {
283+
self.data.extend_from_slice(&data.to_le_bytes());
284+
}
285+
286+
fn reserve_bytes(&mut self, count: usize) -> usize {
287+
let offset = self.data.len();
288+
self.data.resize(offset + count, 0);
289+
offset
290+
}
291+
}
292+
293+
impl<'a> Drop for Member<'a> {
294+
fn drop(&mut self) {
295+
let data_size = self.data.len() - self.header_offset - Self::HEADER_SIZE;
296+
assert!(data_size < i32::MAX as usize);
297+
self.set_decimal_field(48, 10, data_size as i32);
298+
// pad to even address
299+
if data_size % 2 == 1 {
300+
self.data.push(b'\n');
301+
}
302+
}
24303
}
25304
}

0 commit comments

Comments
 (0)