A backend mismatch is refused by the loader, and says so
This commit is contained in:
parent
b47f137b05
commit
fa83056708
29
lib/emit.ml
29
lib/emit.ml
@ -78,6 +78,17 @@ let struct_name_of (t : Types.t) =
|
|||||||
let cellptr n = "@" ^ quoted ("flan.cellp." ^ n)
|
let cellptr n = "@" ^ quoted ("flan.cellp." ^ n)
|
||||||
let globalptr n = "@" ^ quoted ("flan.gp." ^ n)
|
let globalptr n = "@" ^ quoted ("flan.gp." ^ n)
|
||||||
|
|
||||||
|
(* Which backend built this image. A dev build defines its own marker and a
|
||||||
|
redefinition module emits a data relocation against the one it was built
|
||||||
|
for, so a crossed pair — an LLVM module in an [--x86] host, or the reverse —
|
||||||
|
is refused by the loader at [dlopen] instead of running until the first call
|
||||||
|
into a redefined function that takes or returns a struct, which is where the
|
||||||
|
two conventions disagree and where the crossed pair was measured dying with
|
||||||
|
SIGSEGV. See [X86.abi_marker], which is the same mechanism spelled for the
|
||||||
|
other backend, and HANDOFF-x86-abi-marker.md. *)
|
||||||
|
let abi_marker = "flan.abi.llvm"
|
||||||
|
let abi_marker_sym = "@" ^ quoted abi_marker
|
||||||
|
|
||||||
(* ── Types ─────────────────────────────────────────────────────────── *)
|
(* ── Types ─────────────────────────────────────────────────────────── *)
|
||||||
|
|
||||||
let rec ll (t : Types.t) =
|
let rec ll (t : Types.t) =
|
||||||
@ -2914,6 +2925,13 @@ let program ?(checks = true) ?(dev = false) ?(debug = false) ?(pnames = [])
|
|||||||
Nothing has been redefined yet, so a dev build starts out behaving exactly
|
Nothing has been redefined yet, so a dev build starts out behaving exactly
|
||||||
like a release one — the indirection is the only difference. *)
|
like a release one — the indirection is the only difference. *)
|
||||||
if dev then begin
|
if dev then begin
|
||||||
|
(* The ABI marker, defined here so a redefinition module can bind against
|
||||||
|
it, and only in a dev build: a release build has no cells and nothing to
|
||||||
|
load into one, so it keeps exactly the module text it had before this
|
||||||
|
existed. [-rdynamic] is what puts it in the executable's dynamic symbol
|
||||||
|
table, and a dev build is the only build that gets that either. *)
|
||||||
|
Buffer.add_string m.out
|
||||||
|
(Printf.sprintf "%s = global i64 0\n" abi_marker_sym);
|
||||||
List.iter
|
List.iter
|
||||||
(fun (fn : Tast.fn) ->
|
(fun (fn : Tast.fn) ->
|
||||||
Buffer.add_string m.out
|
Buffer.add_string m.out
|
||||||
@ -3029,6 +3047,17 @@ let redefinition ?(checks = true) ?(dev = false) ?(debug = false)
|
|||||||
(globalptr g.Tast.gname)))
|
(globalptr g.Tast.gname)))
|
||||||
p.Tast.globals;
|
p.Tast.globals;
|
||||||
if dev then begin
|
if dev then begin
|
||||||
|
(* The host's ABI marker, and a pointer-sized datum holding its address.
|
||||||
|
That datum is a relocation the loader has to resolve while it maps the
|
||||||
|
object, so a host built by the other backend — which defines
|
||||||
|
[flan.abi.x86] and not this — fails the [dlopen] outright, rather than
|
||||||
|
loading and then dying at the first call into a redefined function that
|
||||||
|
takes or returns a struct. Hidden, so this module's own copy can never
|
||||||
|
be interposed by another loaded module's; the relocation against the
|
||||||
|
host's marker is the only part that matters. *)
|
||||||
|
Buffer.add_string m.out
|
||||||
|
(Printf.sprintf "%s = external global i64\n%s = hidden global ptr %s\n\n"
|
||||||
|
abi_marker_sym ("@" ^ quoted "flan.abi.require") abi_marker_sym);
|
||||||
(* The cells are the host's, like the globals. Referencing one is how a
|
(* The cells are the host's, like the globals. Referencing one is how a
|
||||||
redefined function reaches its siblings, and storing into one is how it
|
redefined function reaches its siblings, and storing into one is how it
|
||||||
replaces itself. A name the host lacks gets a slot instead, filled by
|
replaces itself. A name the host lacks gets a slot instead, filled by
|
||||||
|
|||||||
47
lib/x86.ml
47
lib/x86.ml
@ -408,6 +408,28 @@ let gsym n = asm_sym ("flan." ^ n)
|
|||||||
Byte-for-byte or the link fails and the piece served nothing. *)
|
Byte-for-byte or the link fails and the piece served nothing. *)
|
||||||
let csym n = asm_sym ("flan.cell." ^ n)
|
let csym n = asm_sym ("flan.cell." ^ n)
|
||||||
|
|
||||||
|
(* The marker that says which backend built an image, and it is the whole of
|
||||||
|
the answer to the one way these two backends can be mixed and be wrong.
|
||||||
|
[emit.ml] and this file agree on every scalar and disagree on every
|
||||||
|
aggregate — this file passes a struct by pointer with a hidden [sret] and
|
||||||
|
LLVM classifies per SysV — so a redefinition module from one backend
|
||||||
|
dlopened into a host from the other links, loads, and then dies at the first
|
||||||
|
call into a redefined function that takes or returns a struct. That was
|
||||||
|
measured as SIGSEGV; see HANDOFF-x86-aggregates.md.
|
||||||
|
|
||||||
|
A dev build defines its own marker and a redefinition module emits a data
|
||||||
|
relocation against the marker it was itself built for. A matched pair binds
|
||||||
|
it and notices nothing. A crossed pair has no such symbol to bind, and the
|
||||||
|
loader refuses the module at [dlopen] — before a single instruction of the
|
||||||
|
new body runs, and with the missing symbol naming the backend in the
|
||||||
|
message. That is the property: the mismatch is caught by the loader rather
|
||||||
|
than by the processor, at load rather than at a call.
|
||||||
|
|
||||||
|
[Emit.abi_marker] is the same string for the LLVM half. The two must stay
|
||||||
|
distinct and neither may ever be defined by both backends, or the refusal
|
||||||
|
quietly stops refusing. *)
|
||||||
|
let abi_marker = "flan.abi.x86"
|
||||||
|
|
||||||
(* ── Debug information ───────────────────────────────────────────────── *)
|
(* ── Debug information ───────────────────────────────────────────────── *)
|
||||||
|
|
||||||
(* DWARF, written out as bytes, for the same reason the instructions are — and
|
(* DWARF, written out as bytes, for the same reason the instructions are — and
|
||||||
@ -3266,6 +3288,19 @@ let program ~checks ?(dev = false) ?(debug = false) (p : Tast.program) : string
|
|||||||
(Printf.sprintf "\t.section\t.init_array,\"aw\",@init_array\n\t.align\t8\n%s\
|
(Printf.sprintf "\t.section\t.init_array,\"aw\",@init_array\n\t.align\t8\n%s\
|
||||||
\t.quad\t%s\n\n"
|
\t.quad\t%s\n\n"
|
||||||
(if dev then "\t.quad\tflan_dev_reg_enable\n" else "") init_sym);
|
(if dev then "\t.quad\tflan_dev_reg_enable\n" else "") init_sym);
|
||||||
|
(* The ABI marker, and only in a dev build: it exists for redefinition
|
||||||
|
modules to bind against, a release build has no cells to load one into,
|
||||||
|
and gating it here is what keeps a release build's assembly byte-for-byte
|
||||||
|
what it was. [.globl] and default visibility, for the reason the cells
|
||||||
|
have them — a dlopened object has to be able to see it, which is also why
|
||||||
|
[Build.executable] passes [-rdynamic] for a dev build and nothing else. *)
|
||||||
|
if dev then
|
||||||
|
Buffer.add_string out
|
||||||
|
(Printf.sprintf
|
||||||
|
"\t.data\n\t.globl\t%s\n\t.align\t8\n\t.type\t%s, @object\n\
|
||||||
|
\t.size\t%s, 8\n%s:\n\t.quad\t0\n\n"
|
||||||
|
(asm_sym abi_marker) (asm_sym abi_marker) (asm_sym abi_marker)
|
||||||
|
(asm_sym abi_marker));
|
||||||
if dev then Buffer.add_string out (emit_cells p);
|
if dev then Buffer.add_string out (emit_cells p);
|
||||||
Buffer.add_string out (emit_globals_data md p.Tast.globals);
|
Buffer.add_string out (emit_globals_data md p.Tast.globals);
|
||||||
Buffer.add_string out "\n\t.section\t.rodata\n";
|
Buffer.add_string out "\n\t.section\t.rodata\n";
|
||||||
@ -3423,6 +3458,18 @@ let redefinition ~checks ?(dev = true) ?(known = fun _ -> true)
|
|||||||
"\t.size\tflan_reload_install, . - flan_reload_install\n\n";
|
"\t.size\tflan_reload_install, . - flan_reload_install\n\n";
|
||||||
let out = Buffer.create 8192 in
|
let out = Buffer.create 8192 in
|
||||||
Buffer.add_buffer out text;
|
Buffer.add_buffer out text;
|
||||||
|
(* The ABI marker this module requires of its host. A pointer-sized datum
|
||||||
|
holding the host's marker is a relocation the loader has to resolve while
|
||||||
|
it maps the object, whatever it does about lazy binding of calls, so a
|
||||||
|
host that does not define [flan.abi.x86] fails the [dlopen] outright. A
|
||||||
|
call would do as well under [RTLD_NOW], which is what both loaders here
|
||||||
|
pass, but a datum does not depend on that and costs eight bytes.
|
||||||
|
|
||||||
|
The label is local: nothing outside this module names it, and only the
|
||||||
|
relocation against the marker matters. *)
|
||||||
|
Buffer.add_string out
|
||||||
|
(Printf.sprintf "\n\t.data\n\t.align\t8\n%s:\n\t.quad\t%s\n"
|
||||||
|
(asm_sym "flan.abi.require") (asm_sym abi_marker));
|
||||||
Buffer.add_string out "\n\t.section\t.rodata\n";
|
Buffer.add_string out "\n\t.section\t.rodata\n";
|
||||||
Buffer.add_buffer out rodata;
|
Buffer.add_buffer out rodata;
|
||||||
Buffer.add_string out "\n\t.section\t.note.GNU-stack,\"\",@progbits\n";
|
Buffer.add_string out "\n\t.section\t.note.GNU-stack,\"\",@progbits\n";
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
/* The Flan symbols the executable itself defines. Flan names contain
|
/* The Flan symbols the executable itself defines. Flan names contain
|
||||||
@ -62,11 +63,43 @@ static double now_ms(void) {
|
|||||||
return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
|
return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The same sentence [vendor/agent/flan_agent.c] says, for the same failure.
|
||||||
|
* Duplicated rather than shared: the agent is vendored to be dropped into a
|
||||||
|
* user's game and carries no header of its own, and this host is a test
|
||||||
|
* fixture that links against neither it nor the runtime's dev half.
|
||||||
|
*
|
||||||
|
* A dev build defines a marker naming the backend that built it, and a
|
||||||
|
* redefinition module holds a pointer to the marker it was itself built for.
|
||||||
|
* The two backends agree on scalars and disagree on every aggregate, so a
|
||||||
|
* crossed pair would run until the first call into a redefined function that
|
||||||
|
* takes or returns a struct and then die with SIGSEGV. The marker turns that
|
||||||
|
* into a relocation the loader cannot resolve. What it says then is
|
||||||
|
* "undefined symbol: flan.abi.x86", so the marker's name is matched — not the
|
||||||
|
* loader's phrasing, which is libc's to change — and the reason is stated. */
|
||||||
|
static const char *abi_mismatch(const char *err) {
|
||||||
|
if (err == NULL) return NULL;
|
||||||
|
if (strstr(err, "flan.abi.x86") != NULL)
|
||||||
|
return "the module and this host were built by different backends: the "
|
||||||
|
"module came from the x86 dev backend and needs flan.abi.x86, "
|
||||||
|
"which this host does not define. The two backends pass every "
|
||||||
|
"struct differently. Rebuild the host with --x86.";
|
||||||
|
if (strstr(err, "flan.abi.llvm") != NULL)
|
||||||
|
return "the module and this host were built by different backends: the "
|
||||||
|
"module came from LLVM and needs flan.abi.llvm, which an --x86 "
|
||||||
|
"host does not define. The two backends pass every struct "
|
||||||
|
"differently. Rebuild the host without --x86.";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static int install(const char *path) {
|
static int install(const char *path) {
|
||||||
double t0 = now_ms();
|
double t0 = now_ms();
|
||||||
void *h = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
void *h = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||||
if (h == NULL) {
|
if (h == NULL) {
|
||||||
fprintf(stderr, "dlopen %s: %s\n", path, dlerror());
|
/* [dlerror] is one-shot, so the pointer is taken once and used twice. */
|
||||||
|
const char *err = dlerror();
|
||||||
|
const char *why = abi_mismatch(err);
|
||||||
|
if (why != NULL) fprintf(stderr, "flan: %s\n", why);
|
||||||
|
else fprintf(stderr, "dlopen %s: %s\n", path, err);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
install_fn f = (install_fn)(uintptr_t)dlsym(h, "flan_reload_install");
|
install_fn f = (install_fn)(uintptr_t)dlsym(h, "flan_reload_install");
|
||||||
|
|||||||
@ -335,37 +335,88 @@ let () =
|
|||||||
List.iter (fun p -> try Sys.remove p with Sys_error _ -> ())
|
List.iter (fun p -> try Sys.remove p with Sys_error _ -> ())
|
||||||
[ h; m1; m2; o; e ]
|
[ h; m1; m2; o; e ]
|
||||||
in
|
in
|
||||||
agg_run "llvm" dev (fun q name ->
|
let agg_llvm_mod q name =
|
||||||
let o = tmp name in
|
let o = tmp name in
|
||||||
let ir = Emit.redefinition ~dev:true ~known:agg_known q ~fns:agg_fns in
|
let ir = Emit.redefinition ~dev:true ~known:agg_known q ~fns:agg_fns in
|
||||||
ignore (Build.shared ~opts:dev ~ir ~out:o ());
|
ignore (Build.shared ~opts:dev ~ir ~out:o ());
|
||||||
o);
|
o
|
||||||
agg_run "x86" x86 (fun q name ->
|
in
|
||||||
|
let agg_x86_mod q name =
|
||||||
let o = tmp name in
|
let o = tmp name in
|
||||||
let asm =
|
let asm =
|
||||||
X86.redefinition ~checks:true ~dev:true ~known:agg_known q ~fns:agg_fns
|
X86.redefinition ~checks:true ~dev:true ~known:agg_known q ~fns:agg_fns
|
||||||
in
|
in
|
||||||
ignore (Build.shared_x86 ~opts:x86 ~asm ~out:o ());
|
ignore (Build.shared_x86 ~opts:x86 ~asm ~out:o ());
|
||||||
o);
|
o
|
||||||
(* The mismatch, which is the same measurement run crossed. An --x86 host
|
in
|
||||||
given LLVM-built modules dies with SIGSEGV on the first call into a
|
agg_run "llvm" dev agg_llvm_mod;
|
||||||
redefined aggregate body — measured, not argued:
|
agg_run "x86" x86 agg_x86_mod;
|
||||||
|
|
||||||
|
(* The same measurement run crossed, which is what the marker symbol is
|
||||||
|
for. Before it, an --x86 host given LLVM-built modules loaded them and
|
||||||
|
then died with SIGSEGV on the first call into a redefined aggregate
|
||||||
|
body:
|
||||||
|
|
||||||
got: "a1\nhost 54063108\na1\n" (exit 139)
|
got: "a1\nhost 54063108\na1\n" (exit 139)
|
||||||
|
|
||||||
That is not asserted here. It is undefined behaviour and what it prints
|
That could not be asserted. It was undefined behaviour and what it
|
||||||
is a property of whichever LLVM is installed; a test that pins it would
|
printed was a property of whichever LLVM happened to be installed; a
|
||||||
be pinning the shape of a crash.
|
test pinning it would have been pinning the shape of a crash.
|
||||||
|
|
||||||
What is asserted is narrower, and the gap between the two is the finding
|
It is deterministic now, which is why it is here. A dev build defines a
|
||||||
rather than a caveat on it: [Build.opts] is where the backend choice
|
marker naming the backend that built it — [flan.abi.x86] or
|
||||||
lives, so a builder handed the *other* backend's option record refuses.
|
[flan.abi.llvm] — and a redefinition module holds a pointer to the one
|
||||||
The crossed run above passes both refusals — it hands [Build.shared] an
|
it was itself built for. That pointer is a relocation the loader has to
|
||||||
LLVM record and never calls [Build.shared_x86] at all — and it still
|
resolve while it maps the object, so a crossed pair fails the [dlopen]
|
||||||
segfaults with this guard in place, re-measured after it landed. The
|
outright, before a single instruction of the new body runs. Both
|
||||||
guard catches a caller holding one option record; it cannot catch a
|
directions, because a marker only one of the two backends emitted would
|
||||||
caller holding two, and [flan reload] is exactly that caller. See
|
refuse in one direction and say nothing in the other.
|
||||||
HANDOFF-x86-aggregates.md. *)
|
|
||||||
|
Asserted on the message as well as the exit status, the way the
|
||||||
|
retyped-global and registry-overflow cases below are: a nonzero exit is
|
||||||
|
not by itself this refusal, and the point of the exercise is that what
|
||||||
|
reaches a user names the reason rather than repeating the loader's
|
||||||
|
"undefined symbol". *)
|
||||||
|
let agg_cross label opts mkmod wants =
|
||||||
|
let h = tmp ("agg-xhost-" ^ label) in
|
||||||
|
ignore
|
||||||
|
(Build.executable ~opts ~csrcs:[ "reload_host.c" ] ~lflags:[ "-ldl" ]
|
||||||
|
a1 ~out:h);
|
||||||
|
let m1 = mkmod a1 ("agg-cross-" ^ label ^ "-1.so") in
|
||||||
|
let o = tmp ("agg-xout-" ^ label) and e = tmp ("agg-xerr-" ^ label) in
|
||||||
|
let code =
|
||||||
|
Sys.command
|
||||||
|
(Printf.sprintf "%s %s > %s 2> %s" (Filename.quote h)
|
||||||
|
(Filename.quote m1) (Filename.quote o) (Filename.quote e))
|
||||||
|
in
|
||||||
|
let said = In_channel.with_open_bin e In_channel.input_all in
|
||||||
|
if code = 0 then
|
||||||
|
fail "%s: a crossed pair loaded and ran (exit 0)" label;
|
||||||
|
if not (has said "built by different backends") then
|
||||||
|
fail "%s: a crossed pair was refused without naming the reason: %S"
|
||||||
|
label said;
|
||||||
|
(* Which marker is missing is which backend built the module, so this is
|
||||||
|
also what says the refusal fired for the right direction rather than
|
||||||
|
for the other one. *)
|
||||||
|
if not (has said wants) then
|
||||||
|
fail "%s: the refusal named the wrong marker (wanted %s): %S" label
|
||||||
|
wants said;
|
||||||
|
List.iter (fun p -> try Sys.remove p with Sys_error _ -> ())
|
||||||
|
[ h; m1; o; e ]
|
||||||
|
in
|
||||||
|
(* An --x86 host handed an LLVM module: the pair the CLI can build today,
|
||||||
|
since [flan reload] has no --x86 spelling. *)
|
||||||
|
agg_cross "x86-host-llvm-module" x86 agg_llvm_mod "flan.abi.llvm";
|
||||||
|
(* And the reverse, which no command spells but [X86.redefinition] does. *)
|
||||||
|
agg_cross "llvm-host-x86-module" dev agg_x86_mod "flan.abi.x86";
|
||||||
|
|
||||||
|
(* The option-record guard, which is the older and narrower half of the
|
||||||
|
same answer: [Build.opts] is where the backend choice lives, so a
|
||||||
|
builder handed the *other* backend's option record refuses by name. It
|
||||||
|
catches a caller holding one option record and reaching for the wrong
|
||||||
|
builder. It cannot catch a caller holding two — the crossed runs above
|
||||||
|
pass both of these refusals — which is what the marker is for. See
|
||||||
|
HANDOFF-x86-aggregates.md and HANDOFF-x86-abi-marker.md. *)
|
||||||
(match Build.shared ~opts:x86 ~ir:"" ~out:(tmp "never.so") () with
|
(match Build.shared ~opts:x86 ~ir:"" ~out:(tmp "never.so") () with
|
||||||
| _ -> fail "Build.shared accepted an --x86 option record"
|
| _ -> fail "Build.shared accepted an --x86 option record"
|
||||||
| exception Failure m when has m "--x86" -> ()
|
| exception Failure m when has m "--x86" -> ()
|
||||||
|
|||||||
49
vendor/agent/flan_agent.c
vendored
49
vendor/agent/flan_agent.c
vendored
@ -674,6 +674,49 @@ static void emit(sink *o, const void *p, size_t n) {
|
|||||||
|
|
||||||
static void reply(sink *o, const char *s) { emit(o, s, strlen(s)); }
|
static void reply(sink *o, const char *s) { emit(o, s, strlen(s)); }
|
||||||
|
|
||||||
|
/* A dlopen failure the compiler's two backends are responsible for, turned
|
||||||
|
* into a sentence that says so.
|
||||||
|
*
|
||||||
|
* Flan has two native backends. They agree about every scalar and disagree
|
||||||
|
* about every aggregate — the x86 dev backend passes a struct by pointer with
|
||||||
|
* a hidden sret, LLVM classifies it per the SysV psABI — so a redefinition
|
||||||
|
* module built by one and loaded into a host built by the other links, loads,
|
||||||
|
* and then dies with SIGSEGV at the first call into a redefined function that
|
||||||
|
* takes or returns a struct. A dev build therefore defines a marker naming its
|
||||||
|
* backend and a module holds a pointer to the marker it was built for, which
|
||||||
|
* is a relocation the loader must resolve while it maps the object. A crossed
|
||||||
|
* pair has no such symbol and is refused here, before any of the new code
|
||||||
|
* runs.
|
||||||
|
*
|
||||||
|
* What the loader says at that point is "undefined symbol: flan.abi.x86",
|
||||||
|
* which is true and tells nobody anything. So the marker's name is matched —
|
||||||
|
* the name, not the loader's phrasing, which is libc's to change — and the
|
||||||
|
* reason is stated instead. Which marker is missing says which backend built
|
||||||
|
* the module, and the host is necessarily the other one.
|
||||||
|
*
|
||||||
|
* Returns NULL for a failure that is about something else, which is then
|
||||||
|
* passed through as the loader wrote it. */
|
||||||
|
static const char *abi_mismatch(const char *err) {
|
||||||
|
if (err == NULL) return NULL;
|
||||||
|
if (strstr(err, "flan.abi.x86") != NULL)
|
||||||
|
return "the module and the running program were built by different "
|
||||||
|
"backends: the module came from the x86 dev backend and needs "
|
||||||
|
"flan.abi.x86, which this program does not define. The two "
|
||||||
|
"backends pass every struct differently, so the pair would die at "
|
||||||
|
"the first call into a redefined function that takes or returns "
|
||||||
|
"one. Rebuild the program with --x86 so that both halves agree.";
|
||||||
|
if (strstr(err, "flan.abi.llvm") != NULL)
|
||||||
|
return "the module and the running program were built by different "
|
||||||
|
"backends: the module came from LLVM and needs flan.abi.llvm, "
|
||||||
|
"which an --x86 program does not define. The two backends pass "
|
||||||
|
"every struct differently, so the pair would die at the first call "
|
||||||
|
"into a redefined function that takes or returns one. Rebuild the "
|
||||||
|
"program without --x86: there is no --x86 spelling for building a "
|
||||||
|
"redefinition module yet, so the program is the half that has to "
|
||||||
|
"move.";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* One line, one answer, one module. This is the whole of what the agent is
|
/* One line, one answer, one module. This is the whole of what the agent is
|
||||||
* asked, and it is reached two ways: from the socket below, and — in a build
|
* asked, and it is reached two ways: from the socket below, and — in a build
|
||||||
* where the compiler is a thread in this same process — by being called. The
|
* where the compiler is a thread in this same process — by being called. The
|
||||||
@ -1120,8 +1163,12 @@ static void handle_line(char *line, sink *o) {
|
|||||||
}
|
}
|
||||||
void *h = dlopen(line, RTLD_NOW | RTLD_LOCAL);
|
void *h = dlopen(line, RTLD_NOW | RTLD_LOCAL);
|
||||||
if (h == NULL) {
|
if (h == NULL) {
|
||||||
|
/* [dlerror] is one-shot and the next dl call may clobber what it returned,
|
||||||
|
* so the pointer is taken once and used for both the test and the reply. */
|
||||||
|
const char *err = dlerror();
|
||||||
|
const char *why = abi_mismatch(err);
|
||||||
reply(o, "err ");
|
reply(o, "err ");
|
||||||
reply(o, dlerror());
|
reply(o, why != NULL ? why : err);
|
||||||
reply(o, "\n");
|
reply(o, "\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user