Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ jobs:
- run: | # Build Go test apps.
cd mirrord/layer/tests
../../../scripts/build_go_apps.sh 24
- run: ./mirrord/layer/tests/apps/dlopen_cgo/build_test_app.sh
- uses: actions/setup-go@v5
with:
go-version: "1.25"
Expand Down Expand Up @@ -479,7 +480,6 @@ jobs:
- run: |
cd mirrord/layer/tests/apps/double_listen
cargo build
- run: ./mirrord/layer/tests/apps/dlopen_cgo/build_test_app.sh
- run: ./scripts/build_c_apps.sh
- run: cargo test --target x86_64-unknown-linux-gnu -p mirrord-layer
- name: create dummy file to compile wizard feature
Expand Down
2 changes: 2 additions & 0 deletions changelog.d/+dlopen-many.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added an experimental feature for supporting dlopen multiple c-shared go libraries
for amd64 Linux and go v1.24.* only.
251 changes: 251 additions & 0 deletions mirrord/layer/src/go/c_shared/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) mod go_1_24 {
use std::arch::naked_asm;

use crate::{hooks::HookManager, macros::hook_symbol};

pub(crate) fn hook(hook_manager: &mut HookManager, module_name: &str) {
hook_symbol!(
hook_manager,
module_name,
"internal/runtime/syscall.Syscall6",
internal_runtime_syscall_syscall6_detour
);
}

/// Detour of `internal/runtime/syscall.Syscall6`.
///
/// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr)
/// <https://github.com/golang/go/blob/go1.24.11/src/internal/runtime/syscall/asm_linux_amd64.s#L27>
///
/// Function arguments are mapped as the following:
/// rax = num
/// rbx = a1
/// rcx = a2
/// rdi = a3
/// rsi = a4
/// r8 = a5
/// r9 = a6
/// r14 = g
#[unsafe(naked)]
unsafe extern "C" fn internal_runtime_syscall_syscall6_detour() {
naked_asm!(
// If the syscall is SYS_EXIT or SYS_EXIT_GROUP,
// skip our logic and just execute it.
"cmp rax,60", // SYS_EXIT
"je 2f",
"cmp rax,231", // SYS_EXIT_GROUP
"je 2f",
"push rbp",
"mov rbp,rsp",
// Reservce space and store args.
"sub rsp,0x40",
"mov [rsp+0x38],r9",
"mov [rsp+0x30],r8",
"mov [rsp+0x28],rsi",
"mov [rsp+0x20],rdi",
"mov [rsp+0x18],rcx",
"mov [rsp+0x10],rbx",
"mov [rsp],rax",
// function args starting at rdi.
"mov rdi,rsp",
"call {c_abi_wrapper_on_systemstack}",
// Check failure
"cmp rax,-0xfff",
"jbe 1f",
// Syscall failed.
// Save errno in rcx.
"neg rax",
"mov rcx,rax",
// Fill -1 in rax.
"mov rax,-0x1",
// Clear result register.
"mov rbx,0x0",
// Drop space reserved for locals.
"add rsp,0x40",
// Restore rbp and return.
"pop rbp",
"ret",
// Syscall did not fail. Result is in rax. Clear other result registers.
"1:",
"mov rbx,0x0",
"mov rcx,0x0",
// Drop space for storing args locally.
"add rsp,0x40",
// Restore rbp and return.
"pop rbp",
"ret",
// Move the first syscall argument to the correct register (rdx),
// and execute the syscall.
"2:",
"mov rdx,rdi",
"syscall",

c_abi_wrapper_on_systemstack = sym c_abi_wrapper_on_systemstack,
);
}

/// Calls [`c_abi_wrapper`] on systemstack.
///
/// Implemented based on [`runtime.systemstack`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L483)
#[unsafe(naked)]
unsafe extern "C" fn c_abi_wrapper_on_systemstack() {
naked_asm!(
// This is required, as we call `gosave_systemstack_switch` later.
// Not having any local variables in this function is required as well.
"push rbp",
"mov rbp,rsp",
// We assume that r14 stores the address of g.
// Load address of current m to rbx.
// https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L410
"mov rbx,[r14+0x30]",
// Check if g is m->gsignal. If so, do not switch stack.
// https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L536
"cmp r14,[rbx+0x50]",
"je 1f",
// Load address of m->g0 to rdx.
// Check if g is g0. If so, do not switch stack.
"mov rdx,[rbx]",
"cmp r14,rdx",
"je 1f",
// We expect g is m->curg now. If not, abort with `ud2`.
// https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L541
"cmp r14,[rbx+0xc0]",
"jne 2f",
// Switch to system stack.
"call {gosave_systemstack_switch}",
// rdx holds the address of m->g0. Store it also in TLS and r14.
"mov fs:0xfffffffffffffff8,rdx",
"mov r14,rdx",
// Fill rsp with g0's g->sched->sp. After this, we are on system stack.
"mov rsp,[rdx+0x38]",
// We assume rdi still has the original syscall args stored on user g stack.
"call {c_abi_wrapper}",
// Switch back to g stack.
// https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L516-L526
// We assume r14 still has g0. Store g0's m in rbx.
"mov rbx,[r14+0x30]",
// Store m->curg in rsi.
"mov rsi,[rbx+0xc0]",
// Store user g in TLS
"mov fs:0xfffffffffffffff8,rsi",
// Store user g in r14
"mov r14,rsi",
// Fill rsp with g->sched->sp
"mov rsp,[rsi+0x38]",
// Fill rbp with g->sched->bp
// https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L317
"mov rbp,[rsi+0x68]",
"mov QWORD PTR [rsi+0x38],0x0",
"mov QWORD PTR [rsi+0x68],0x0",
// Restore rbp and return.
"pop rbp",
"ret",
// Already on system stack. Tail call `c_abi_wrapper`.
// https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L528-L537
"1:",
"pop rbp",
"jmp {c_abi_wrapper}",
// Abort the program.
"2:",
"ud2",
gosave_systemstack_switch = sym gosave_systemstack_switch,
c_abi_wrapper = sym c_abi_wrapper,
);
}

/// Exact copy of the `go_1_25::c_abi_wrapper` function.
///
/// Move function args from stack to registers so we can call
/// [`c_abi_syscall6_handler`](crate::go::c_abi_syscall6_handler).
///
/// We expect rdi stores the address of the start of function args on the stack.
///
/// C ABI: fn(rdi, rsi, rdx, rcx, r8, r9, stack)
#[unsafe(naked)]
unsafe extern "C" fn c_abi_wrapper() {
naked_asm!(
"push rbp",
"mov rbp,rsp",
"sub rsp,0x8",
"and rsp,-0x10",
"mov rax,[rdi]",
"mov rsi,[rdi+0x10]",
"mov rdx,[rdi+0x18]",
"mov rcx,[rdi+0x20]",
"mov r8,[rdi+0x28]",
"mov r9,[rdi+0x30]",
"mov r10,[rdi+0x38]",
"mov [rsp],r10",
"mov rdi,rax",
"call c_abi_syscall6_handler",
"mov rsp, rbp",
"pop rbp",
"ret"
);
}

/// Implemented based on [`gosave_systemstack_switch`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L823)
///
/// Smashes r9.
#[unsafe(naked)]
unsafe extern "C" fn gosave_systemstack_switch() {
naked_asm!(
// TODO: In Go's implementation, it loads the address of `runtime.systemstack_switch` + 8 bytes.
// [`runtime.systemstack_switch`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L475)
// is a dummy marker function. If it is directly called, it will hit a `ud2` trap.
// Go only cares that g->sched->pc is set to an address between the prologue and epilogue.
//
// I don't know if it matters for the address to be between the actual `systemstack_switch` function.
// Or a simple dummy function will work?
"lea r9, [rip + {dummy} + 0x8]",
Comment thread
0x00A5 marked this conversation as resolved.
// Store r9 in g->sched->pc.
"mov QWORD PTR [r14+0x40],r9",
"lea r9, [rsp+0x8]",
// Store r9 in g->sched->sp.
"mov QWORD PTR [r14+0x38],r9",
// Store 0 in g->sched->ret.
"mov QWORD PTR [r14+0x58],0x0",
// Store rbp in g->sched->bp.
"mov QWORD PTR [r14+0x68],rbp",
// Store g->sched->ctxt in r9.
"mov r9, QWORD PTR [r14+0x50]",
// If g->sched->ctxt == 0 abort runtime, otherwise return.
"test r9, r9",
"jz 1f",
"call {runtime_abort}",
"1:",
Comment on lines +213 to +217
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Logic Error: Inverted condition comment

The comment states "If g->sched->ctxt == 0 abort runtime, otherwise return" but the code does the opposite:

  • jz 1f jumps to return (label 1) when r9 is zero
  • Falls through to call {runtime_abort} when r9 is non-zero

This means the code aborts when ctxt != 0, not when ctxt == 0. The comment is backwards and misleading.

// Store g->sched->ctxt in r9.
"mov    r9, QWORD PTR [r14+0x50]",
// If g->sched->ctxt != 0 abort runtime, otherwise return.
"test   r9, r9",
"jz     1f",
"call   {runtime_abort}",

This documentation error could cause future maintainers to misunderstand the code's behavior and introduce bugs when modifying it.

Suggested change
// If g->sched->ctxt == 0 abort runtime, otherwise return.
"test r9, r9",
"jz 1f",
"call {runtime_abort}",
"1:",
// If g->sched->ctxt != 0 abort runtime, otherwise return.
"test r9, r9",
"jz 1f",
"call {runtime_abort}",
"1:",

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

"ret",
Comment on lines +211 to +218
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Comment on line 213 is inverted — code aborts when ctxt != 0, not when == 0.

test r9, r9 / jz 1f jumps to ret when r9 == 0. The call runtime_abort is reached when r9 != 0. The comment says the opposite.

📝 Proposed fix
-            // If g->sched->ctxt == 0 abort runtime, otherwise return.
+            // If g->sched->ctxt != 0 abort runtime, otherwise return.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Store g->sched->ctxt in r9.
"mov r9, QWORD PTR [r14+0x50]",
// If g->sched->ctxt == 0 abort runtime, otherwise return.
"test r9, r9",
"jz 1f",
"call {runtime_abort}",
"1:",
"ret",
// Store g->sched->ctxt in r9.
"mov r9, QWORD PTR [r14+0x50]",
// If g->sched->ctxt != 0 abort runtime, otherwise return.
"test r9, r9",
"jz 1f",
"call {runtime_abort}",
"1:",
"ret",
🤖 Prompt for AI Agents
In `@mirrord/layer/src/go/c_shared/mod.rs` around lines 211 - 218, The inline
comment is inverted for the assembly sequence that loads g->sched->ctxt into r9
and then does "test r9, r9" / "jz 1f" / "call {runtime_abort}" / "1: ret";
update the comment near that block (the lines around "mov    r9, QWORD PTR
[r14+0x50]", "test   r9, r9", "jz     1f", "call   {runtime_abort}", "1:",
"ret") so it correctly states that when ctxt == 0 the code returns and when ctxt
!= 0 the runtime_abort is called (e.g., "If g->sched->ctxt == 0 return,
otherwise call runtime_abort.").


runtime_abort = sym runtime_abort,
dummy = sym dummy,
);
}

/// Implemented based on [`runtime.abort`](https://github.com/golang/go/blob/fed3b0a298464457c58d1150bdb3942f22bd6220/src/runtime/asm_amd64.s#L1237)
///
/// This function crashes the runtime. `int3` is recognized by debuggers.
/// The rest of the function is an infinite trap loop.
#[unsafe(naked)]
unsafe extern "C" fn runtime_abort() {
naked_asm!("int3", "1:", "jmp 1b",);
}

/// A dummy function that wraps some `nop` between prologue and epilogue.
#[unsafe(naked)]
unsafe extern "C" fn dummy() {
naked_asm!(
"push rbp",
"mov rbp,rsp",
"nop",
"nop",
"nop",
"nop",
"nop", // this is the address we save in g->sched->pc
"nop",
"nop",
"pop rbp",
"ret",
);
}
}
Comment on lines +188 to +251
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Go runtime g->sched->pc stack scanning requirements systemstack_switch

💡 Result:

In the Go runtime, g.sched is the “saved register context” the runtime/GC uses to start unwinding (and thus scanning) a goroutine’s user stack when that goroutine isn’t actively running on its own stack.

What stack scanning needs from g->sched

  • When a goroutine is not running, stack unwinding initializes its starting (pc, sp[, lr]) from:
    • g.syscallpc/g.syscallsp if the goroutine is in a syscall, else
    • g.sched.pc/g.sched.sp (and g.sched.lr on LR architectures).
      This is exactly what the unwinder does. (go.dev)
  • Therefore, g.sched.pc must be a valid Go PC suitable for unwinding/stack maps, and g.sched.sp must match the stack pointer for that PC’s frame (same for bp/lr where used). In syscall transitions, the runtime explicitly captures caller pc/sp/bp and saves them via save(pc, sp, bp), then copies g.sched.* into g.syscall* for later unwinding/scanning. (go.dev)

Why systemstack_switch exists (and why g.sched.pc is set to it)

  • systemstack(fn) switches execution from a user goroutine stack to the per-thread system stack (g0) for runtime work that must not grow the user stack / must not be preempted. (go.dev)
  • While doing that switch, the assembly saves the current goroutine state into g->sched and “pretends” the saved pc is systemstack_switch:
    “save our state in g->sched. Pretend to be systemstack_switch if the G stack is scanned.” (go.dev)
  • systemstack_switch is a dummy frame marker left at the bottom of the goroutine stack so the runtime can distinguish:
    • the marker at the bottom of the user goroutine stack vs
    • the real systemstack frame at the top of the system (g0) stack, which is treated as terminating the stack walk (“see topofstack()”). (go.dev)
  • On amd64, the runtime further notes: the systemstack_switch frame layout must match systemstack so unwinding/scanning logic can safely treat the saved context as that marker frame. (tip.golang.org)

Key takeaway

When the runtime switches stacks (e.g., systemstack), it must leave behind a self-consistent saved context in g.sched. Setting g.sched.pc to systemstack_switch is a deliberate requirement so that GC/unwind code can safely interpret the boundary between user-stack frames and system-stack frames during stack walking/scanning. (go.dev)

Citations:


The dummy function approach for g->sched->pc has correctness issues — the TODO identifies a critical problem that needs resolution.

Go's runtime requires g->sched->pc to be a valid, properly-formed Go PC suitable for unwinding and stack map interpretation. More importantly, systemstack_switch is not just any address between a prologue/epilogue — it is a dummy frame marker that the GC and stack unwinding code must be able to distinguish during stack walking to correctly interpret the boundary between user-stack and system-stack frames.

The current approach of using an arbitrary NOP-sled dummy function does not guarantee the correct frame layout that the unwinder expects. This mismatch could cause GC/unwinding failures during stack scanning, potentially leading to crashes or memory corruption. The implementation should either:

  1. Use the actual runtime.systemstack_switch address (if possible from the running Go process), or
  2. Ensure the dummy function's frame layout precisely matches what the unwinder expects for a systemstack_switch frame boundary.

This is not a pragmatic workaround — it is a correctness issue that should be resolved before merging.

🤖 Prompt for AI Agents
In `@mirrord/layer/src/go/c_shared/mod.rs` around lines 188 - 251, The
g->sched->pc must point to a real runtime.systemstack_switch-style marker, not
an arbitrary NOP sled; update gosave_systemstack_switch so the "lea r9, [rip +
{dummy} + 0x8]" uses the actual systemstack_switch marker or a synthetic symbol
whose frame layout exactly matches Go's systemstack_switch frame: replace the
dummy symbol with a symbol that either references the real
runtime.systemstack_switch (if available from the Go runtime) or implement a new
naked function whose prologue/epilogue, saved registers and frame size match the
Go runtime's systemstack_switch marker (match its return address offset and
frame layout used by unwinder), and keep runtime_abort and
gosave_systemstack_switch logic but point g->sched->pc to that correct symbol
instead of the current dummy.

6 changes: 6 additions & 0 deletions mirrord/layer/src/go/linux_x64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,14 @@ pub(crate) fn enable_hooks_in_loaded_module(hook_manager: &mut HookManager, modu
return;
};

tracing::trace!(version, module_name, "Detected Go");
if version >= 1.25 {
go_1_25::hook_in_module(hook_manager, module_name.as_str());
} else if version >= 1.24 {
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
crate::go::c_shared::go_1_24::hook(hook_manager, module_name.as_str());
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
post_go1_23(hook_manager, Some(module_name.as_str()));
} else if version >= 1.23 {
post_go1_23(hook_manager, Some(module_name.as_str()));
} else if version >= 1.19 {
Expand Down
5 changes: 5 additions & 0 deletions mirrord/layer/src/go/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ use tracing::trace;

use crate::{close_detour, file::hooks::*, hooks::HookManager, socket::hooks::*};

#[cfg(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
target_os = "linux"
))]
pub(crate) mod c_shared;
#[cfg_attr(
all(target_os = "linux", target_arch = "x86_64"),
path = "linux_x64.rs"
Expand Down
5 changes: 3 additions & 2 deletions mirrord/layer/tests/apps/dlopen_cgo/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.so
*.h
out.cpp_dlopen_cgo
*.a
libgo*.h
out.*
75 changes: 60 additions & 15 deletions mirrord/layer/tests/apps/dlopen_cgo/build_test_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,76 @@ set -euo pipefail
# Resolve directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

GO_FILE="$SCRIPT_DIR/main.go"
CPP_FILE="$SCRIPT_DIR/main.cpp"
SO_FILE="$SCRIPT_DIR/libgo_server.so"
OUT_BIN="$SCRIPT_DIR/out.cpp_dlopen_cgo"
SERVER_GO_FILE="$SCRIPT_DIR/server/main.go"
SERVER_CPP_FILE="$SCRIPT_DIR/server/lib.cpp"
SERVER_C_SHARED_LIB="$SCRIPT_DIR/server/libgo_server_c_shared.so"
SERVER_C_ARCHIVE_LIB="$SCRIPT_DIR/server/libgo_server_c_archive.a"
SERVER_CPP_WRAPPER_LIB="$SCRIPT_DIR/server/libcpp_server.so"

FILEOPS_GO_FILE="$SCRIPT_DIR/fileops/main.go"
FILEOPS_CPP_FILE="$SCRIPT_DIR/fileops/lib.cpp"
FILEOPS_C_SHARED_LIB="$SCRIPT_DIR/fileops/libgo_fileops_c_shared.so"
FILEOPS_C_ARCHIVE_LIB="$SCRIPT_DIR/fileops/libgo_fileops_c_archive.a"
FILEOPS_CPP_WRAPPER_LIB="$SCRIPT_DIR/fileops/libcpp_fileops.so"

C_SHARED_CPP_FILE="$SCRIPT_DIR/main_c_shared.cpp"
C_SHARED_OUT_BIN="$SCRIPT_DIR/out.dlopen_cgo_c_shared"
C_ARCHIVE_CPP_FILE="$SCRIPT_DIR/main_c_archive.cpp"
C_ARCHIVE_OUT_BIN="$SCRIPT_DIR/out.dlopen_cpp_wrapper_cgo_c_archive"

echo "Script directory: $SCRIPT_DIR"

# Ensure files exist
if [[ ! -f "$GO_FILE" ]]; then
echo "ERROR: main.go not found in $SCRIPT_DIR"
if [[ ! -f "$SERVER_GO_FILE" ]]; then
echo "ERROR: server/main.go not found in $SCRIPT_DIR"
exit 1
fi

if [[ ! -f "$CPP_FILE" ]]; then
if [[ ! -f "$FILEOPS_GO_FILE" ]]; then
echo "ERROR: fileops/main.go not found in $SCRIPT_DIR"
exit 1
fi

if [[ ! -f "$C_SHARED_CPP_FILE" ]]; then
echo "ERROR: main.cpp not found in $SCRIPT_DIR"
exit 1
fi
Comment on lines +51 to 54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading error message and missing check for c-archive source.

Line 38 prints "main.cpp not found" but C_SHARED_CPP_FILE points to main_c_shared.cpp. Also, there is no existence check for C_ARCHIVE_CPP_FILE (main_c_archive.cpp) — if it's missing, the build on line 73 will fail with a less informative compiler error.

Proposed fix
 if [[ ! -f "$C_SHARED_CPP_FILE" ]]; then
-    echo "ERROR: main.cpp not found in $SCRIPT_DIR"
+    echo "ERROR: main_c_shared.cpp not found in $SCRIPT_DIR"
+    exit 1
+fi
+
+if [[ ! -f "$C_ARCHIVE_CPP_FILE" ]]; then
+    echo "ERROR: main_c_archive.cpp not found in $SCRIPT_DIR"
     exit 1
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [[ ! -f "$C_SHARED_CPP_FILE" ]]; then
echo "ERROR: main.cpp not found in $SCRIPT_DIR"
exit 1
fi
if [[ ! -f "$C_SHARED_CPP_FILE" ]]; then
echo "ERROR: main_c_shared.cpp not found in $SCRIPT_DIR"
exit 1
fi
if [[ ! -f "$C_ARCHIVE_CPP_FILE" ]]; then
echo "ERROR: main_c_archive.cpp not found in $SCRIPT_DIR"
exit 1
fi
🤖 Prompt for AI Agents
In `@mirrord/layer/tests/apps/dlopen_cgo/build_test_app.sh` around lines 37 - 40,
The error message for the shared C++ source is misleading and there's no
pre-check for the c-archive source; update the check that uses C_SHARED_CPP_FILE
to print the correct filename (main_c_shared.cpp) or a generic variable-based
message and add an analogous existence check for C_ARCHIVE_CPP_FILE
(main_c_archive.cpp), exiting with a clear error if missing so the build step
that compiles the archive (invoking the compiler for C_ARCHIVE_CPP_FILE) fails
fast with a helpful message.


echo "Building Go shared library..."
go build -buildmode=c-shared -o "$SO_FILE" "$GO_FILE"
go version

# Build app dlopen c-shared go lib
echo "Building Go c-shared server library..."
go build -buildmode=c-shared -o "$SERVER_C_SHARED_LIB" "$SERVER_GO_FILE"

echo "Building Go c-shared file ops library..."
go build -buildmode=c-shared -o "$FILEOPS_C_SHARED_LIB" "$FILEOPS_GO_FILE"

echo "Building C++ c-shared loader app..."
g++ "$C_SHARED_CPP_FILE" -o "$C_SHARED_OUT_BIN" -ldl

echo "Done! c-shared artifacts:"
echo " - $SERVER_C_SHARED_LIB"
echo " - $FILEOPS_C_SHARED_LIB"
echo " - $C_SHARED_OUT_BIN"

# Build app dlopen dynamic cpp lib that uses c-archive go lib
echo "Building Go c-archive server library..."
go build -buildmode=c-archive -o "$SERVER_C_ARCHIVE_LIB" "$SERVER_GO_FILE"

echo "Building C++ server dynamic library..."
g++ -fPIC -shared $SERVER_CPP_FILE $SERVER_C_ARCHIVE_LIB -o $SERVER_CPP_WRAPPER_LIB -lpthread -ldl

echo "Building Go c-archive file ops library..."
go build -buildmode=c-archive -o "$FILEOPS_C_ARCHIVE_LIB" "$FILEOPS_GO_FILE"

echo "Building C++ file ops dynamic library..."
g++ -fPIC -shared $FILEOPS_CPP_FILE $FILEOPS_C_ARCHIVE_LIB -o $FILEOPS_CPP_WRAPPER_LIB -lpthread -ldl

echo "Building C++ loader app..."
g++ "$CPP_FILE" -o "$OUT_BIN" -ldl
echo "Building C++ c-archive loaded app..."
g++ $C_ARCHIVE_CPP_FILE -o $C_ARCHIVE_OUT_BIN -ldl

echo "Done!"
echo "Artifacts:"
echo " - $SO_FILE"
echo " - $OUT_BIN"
echo "Done! c-archive artifacts:"
echo " - $SERVER_C_ARCHIVE_LIB"
echo " - $SERVER_CPP_WRAPPER_LIB"
echo " - $FILEOPS_C_ARCHIVE_LIB"
echo " - $FILEOPS_CPP_WRAPPER_LIB"
Loading
Loading