Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions libwild/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ zstd = { workspace = true }
[target.'cfg(all(target_os = "linux", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
perf-event = { workspace = true }

[build-dependencies]
cc = "1.1.12"

[dev-dependencies]
ar = { workspace = true }
tempfile = { workspace = true }
Expand Down
7 changes: 7 additions & 0 deletions libwild/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
if std::env::var("CARGO_FEATURE_PLUGINS").is_ok() {
cc::Build::new()
.file("src/plugin_message_shim.c")
.compile("plugin_message_shim");
}
}
43 changes: 25 additions & 18 deletions libwild/src/linker_plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,12 @@ impl LoadedPlugin {
// Linker plugins handle entries of this vector serially, which means the message callback
// should be registered first. Otherwise, they won't be able to indicate the problem with
// entries preceding the callback and, for example, silently skip invalid arguments.
transfer_vector.push(LdPluginTv::fn_ptr2(Tag::Message, message));
// The message callback is variadic (printf-style), so we register the C trampoline
// directly as a raw pointer value rather than going through fn_ptr2.
transfer_vector.push(LdPluginTv {
tag: Tag::Message as u32,
value: wild_plugin_message_callback as *const () as usize,
});

for arg in &args.plugin_args {
transfer_vector.push(LdPluginTv::c_str(Tag::Option, arg));
Expand Down Expand Up @@ -992,26 +997,28 @@ extern "C" fn add_input_library(lib_name: *const libc::c_char) -> Status {
Status::Ok
}

/// This function is called when the plugin wants to emit a message. It's supposed to accept varargs
/// similar to printf. Unfortunately that's not exactly easy for us to do, so we just report the
/// format string.
extern "C" fn message(level: libc::c_int, format: *const libc::c_char) -> Status {
catch_panics(|| {
let Some(level) = MessageLevel::from_raw(level) else {
return Status::Err;
};
unsafe extern "C" {
/// C trampoline that accepts the plugin's printf-style varargs, formats them via vsnprintf,
/// then calls `wild_handle_plugin_message` with the resulting string. Defined in
/// `plugin_message_shim.c`.
fn wild_plugin_message_callback(level: libc::c_int, fmt: *const libc::c_char, ...);
}

/// Called by the C shim `wild_plugin_message_callback` with the already-formatted message string.
/// The `no_mangle` is required so the C shim can link against it by name.
#[unsafe(no_mangle)]
extern "C" fn wild_handle_plugin_message(level: libc::c_int, message: *const libc::c_char) {
let Some(level) = MessageLevel::from_raw(level) else {
return;
};

let format = unsafe { CStr::from_ptr(format) };
let text = unsafe { CStr::from_ptr(message) }.to_string_lossy();

if level == MessageLevel::Error || level == MessageLevel::Fatal {
println!("Linker plugin {level}: {}", format.to_string_lossy());
ERROR_MESSAGE.replace(Some(format.to_string_lossy().to_string()));
} else {
println!("Linker plugin {level}: {}", format.to_string_lossy());
}
println!("Linker plugin {level}: {text}");

Status::Ok
})
if level == MessageLevel::Error || level == MessageLevel::Fatal {
ERROR_MESSAGE.replace(Some(text.into_owned()));
}
}

/// Runs `body`, catching any panics. In the case of a panic, the return status is changed to an
Expand Down
46 changes: 46 additions & 0 deletions libwild/src/plugin_message_shim.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Trampoline for the linker plugin message callback.
*
* The Gold plugin API defines the message callback as:
* ld_plugin_status (*ld_plugin_message)(int level, const char *format, ...)
*
* Rust cannot implement C variadic functions on stable, so we implement the
* callback here in C. It formats the printf-style message and forwards the
* result to wild_handle_plugin_message (defined in linker_plugins.rs).
*/

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

/* Rust function we call with the fully-formatted message string. */
extern void wild_handle_plugin_message(int level, const char *message);

void wild_plugin_message_callback(int level, const char *fmt, ...) {
va_list args1, args2;
va_start(args1, fmt);
va_copy(args2, args1);

int len = vsnprintf(NULL, 0, fmt, args1);
va_end(args1);

if (len < 0) {
va_end(args2);
/* Fall back to the raw format string if sizing failed. */
wild_handle_plugin_message(level, fmt);
return;
}

char *buf = malloc((size_t)len + 1);
if (buf == NULL) {
va_end(args2);
wild_handle_plugin_message(level, fmt);
return;
}

vsnprintf(buf, (size_t)len + 1, fmt, args2);
va_end(args2);

wild_handle_plugin_message(level, buf);
free(buf);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
//#SkipLinker:ld
//#LinkArgs:-Wl,-znow -flto -nostdlib -Wl,-plugin-opt=jobs=foo
//#Archive:empty.c:-flto
//#ExpectError:(Error from linker plugin: Invalid parallelism level: \%s|Wild was compiled without linker-plugin support)
//#ExpectError:(Error from linker plugin: Invalid parallelism level: (foo|\%s)|Wild was compiled without linker-plugin support)

#include "runtime.h"

Expand Down
Loading