Magika là một thư viện do Google phát triển, cho phép nhận diện loại file dựa trên nội dung thực tế (content-based file type detection) thay vì chỉ dựa vào phần mở rộng. Ở tầng kỹ thuật, Magika sử dụng machine learning model để phân tích byte stream của file, giúp phát hiện chính xác các định dạng phức tạp hoặc bị ngụy trang.
Tổng quan và Phạm vi kỹ thuật
Trong repository của thư viện, phần Go được cung cấp kèm theo Dockerfile và hướng dẫn build chủ yếu dành cho Linux, thông qua môi trường container. Điều này giúp việc build trên Linux trở nên tương đối dễ dàng, nhưng lại để ngỏ một khoảng trống lớn:
- Không có hướng dẫn build trực tiếp trên macOS và Windows
- Việc xử lý CGO + thư viện native (ONNX Runtime) hoàn toàn bị “ẩn” phía sau Docker
Mục tiêu:
Hướng dẫn build và chạy một sản phẩm sử dụng Magika trên macOS, không thông qua Docker, đồng thời giải thích rõ những gì đang xảy ra ở tầng compile – link – runtime, thay vì chỉ đưa ra một câu lệnh “chạy được”

Trong bài viết, chúng ta sẽ sử dụng Go phiên bản 1.25.4 trên macOS arm64 với example chính thức trong repository của Magika làm ví dụ minh họa: https://github.com/google/magika/blob/main/go/example/main.go
TL;DR
Các bước cần làm khi build chương trình Go sử dụng Magika bao :
| Chuẩn bị thư viện | Đảm bảo onnxruntime trong thư mục /opt/onnxruntime
Chương trình cần tìm được path đến folder assets chứa model |
| Biên dịch (compile) | CGO_CFLAGS=-I/opt/onnxruntime/include \
CGO_ENABLED=1 \ go build -tags onnxruntime -ldflags=”-linkmode=external ‘-extldflags=-L/opt/onnxruntime/lib -Wl,-rpath,/opt/onnxruntime/lib'” -o run_example github.com/google/magika/go/example |
| Chạy chương trình | Có thể chạy chương trình ở bất kỳ đâu.
Nhưng vẫn phải đảm bảo môi trường máy chạy chương trình có |
Ngoài ra, trong bài này, chúng ta cũng sẽ đi qua cách có thể tạo bundle thư viện với chương trình Go. Khi release portable này cho người dùng, file có thể chạy mượt mà không cần tải thêm thư viện
Một số thuật ngữ chính
| Thuật ngữ | Giải nghĩa |
|---|---|
| Liên kết động (Dynamic linking) | Kỹ thuật cho phép các chương trình sử dụng chung các thư viện mã nguồn (dylib trên macOS/iOS) tại thời điểm chạy (runtime) thay vì nhúng toàn bộ mã vào tệp thực thi khi biên dịch, giúp:
|
| dyld | Trình liên kết động của Apple, có nhiệm vụ tải và liên kết các thư viện (.dylib), framework cần thiết vào một ứng dụng khi nó bắt đầu chạy |
| rpath (runpath search path) | Dùng bởi dyld để load các thư viện động |
1. Bối cảnh
Về bản chất, thư viện Go của Magika không phải là Go thuần.
Magika sử dụng:
- ONNX Runtime – một runtime inference engine viết bằng C/C++
- Giao tiếp giữa Go và ONNX Runtime thông qua CGO
Điều này kéo theo một số hệ quả quan trọng:
- Go compiler bắt buộc phải gọi C/C++ compiler
- Binary được build ra không còn là static Go binary
- Việc build và chạy phụ thuộc vào cơ chế dynamic linking của từng hệ điều hành

Cụ thể:
- Windows: sử dụng dynamic library với đuôi
.dll - Linux: sử dụng shared object với đuôi
.so - macOS: sử dụng dynamic library với đuôi
.dylib
Chính sự khác biệt này là nguyên nhân khiến việc build sản phẩm với Magika trở nên phức tạp, đặc biệt trên macOS — và cũng là trọng tâm của bài viết này.
2. Ba giai đoạn quan trọng khi build CGO
Khi build một chương trình CGO, luôn có 3 giai đoạn độc lập:
2.1 Compile (biên dịch C)
C compiler cần tìm thấy header onnxruntime_c_api.h
→ Chúng ta cần CGO_CFLAGS
2.2 Link (liên kết)
Linker cần biết:
- thư viện nằm ở đâu (ví dụ:
.dylib) → cầnL/path/to/lib - dùng external linker (vì có C++) → cần
linkmode=external
2.3 Runtime (chạy chương trình)
Đối với macOS, dyld phải tìm được .dylib
- Điều này khác với việc dùng
LD_LIBRARY_PATHtrên Linux - Phụ thuộc vào RPATH được embed trong binary
Nếu nhầm lẫn 3 giai đoạn này, chúng ta sẽ khó tìm được cách để chạy được sản phẩm hoàn chỉnh.
3. Bước đầu: build thành công nhưng chạy bị crash
3.1 Chuẩn bị thư viện
- Trước hết, hãy đảm bảo đã tải ONNXRuntime về máy.
- Bước 1: tải phiên bản osx-arm64 từ https://github.com/microsoft/onnxruntime/releases. Ví dụ:
curl -L -o onnxruntime-osx-arm64-1.23.2.tgz <https://github.com/microsoft/onnxruntime/releases/download/v1.23.2/onnxruntime-osx-arm64-1.23.2.tgz> tar -xzf onnxruntime-osx-arm64-1.23.2.tgz - Bước 2: giải nén và chuyển vào trong /opt/onnxruntime
sudo mkdir -p /opt/onnxruntime sudo cp -R onnxruntime-osx-arm64-1.23.2/* /opt/onnxruntime/ - Chúng ta sẽ thấy có 2 folder
includevàlibtrong/opt/onnxruntime/- folder
includebao gồm các file header định nghĩa API để binary có thể tương tác với ONNX runtime. Bước sử dụng các file này sẽ xảy ra ở bước compile. - folder
libbao gồm các thư viện động được binary sử dụng lúc runtime
- folder
- Bước 1: tải phiên bản osx-arm64 từ https://github.com/microsoft/onnxruntime/releases. Ví dụ:
- Trong file
go/example/main.go, chúng ta sẽ cần đảm bảoassetsDirđã chứa thư mục assets (bao gồm các model) mà Magika cần load. Trong repo đã có sẵn thư mục assets: https://github.com/google/magika/tree/main/assets.
3.2 Biên dịch và thực thi
Tiếp theo, chúng ta đi vào trong thư mục go của repo Magika có chứa go.mod. Và như đã nói ở bước 2, chúng ta sẽ cần compile với lệnh sau:
CGO_CFLAGS=-I/opt/onnxruntime/include \
CGO_ENABLED=1 \
go build -tags onnxruntime -ldflags="-linkmode=external -extldflags=-L/opt/onnxruntime/lib" -o run_example github.com/google/magika/go/example
Sau khi build xong, khi chạy run_example , sẽ xuất hiện lỗi
dyld[73938]: Library not loaded: @rpath/libonnxruntime.1.23.2.dylib
Referenced from: <9CA49E02-0272-3872-EF68-5D2744B7300C> <path đến run_example>
Reason: no LC_RPATH's found
[1] 73938 abort ./run_example
Đây là lỗi runtime, không phải compile hay link.
Điều này cho biết:
- Binary đã link thành công
- Nhưng khi chạy,
dyldthấy@rpath/...nhưng lại không có LC_RPATH
4. Hiểu đúng @rpath trên macOS
Trên macOS (Mach-O):
.dylibthường được link dưới dạng@rpath/libxxx.dylib@rpath(Runpath Search Path) là placeholder (giống các path variable), được resolve bằng các LC_RPATH embed trong binary
Nếu @rpath không được phân giải đúng để tìm thư viện cần thiết, chương trình sẽ crash.
5. Đặt ra giả thuyết
Tại sao lỗi xảy ra dù đã chỉ định -L/opt/onnxruntime/lib?
Bên Dockerfile môi trường Linux, chúng ta sẽ cần phải set LD_LIBRARY_PATH để trỏ đến thư mục chứa lib của onnxruntime.
Tuy nhiên, biến môi trường này lại không được sử dụng trong macOS, thay vào đó là DYLD_LIBRARY_PATH. Khi System Integrity Protection (SIP) của macOS được bật, biến môi trường này sẽ bị bỏ qua khi đang chạy các tiến trình được bảo vệ (theo guide Runtime Protection của SIP)
Điều này dẫn đến khi binary run_example khi chạy sẽ không tìm được thư viện cần thiết.
6. Xác nhận giả thuyết
6.1 Kiểm tra thư viện được link
otool -L run_example
Kết quả cho thấy binary có sử dụng @rpath/libonnxruntime.1.23.2.dylib
run_example:
@rpath/libonnxruntime.1.23.2.dylib (compatibility version 0.0.0, current version 1.23.2)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 2420.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)
6.2 Kiểm tra RPATH trong binary
otool -l run_example | grep -A2 LC_RPATH
Kết quả: Không có output. Điều này xác nhận binary không có RPATH
💡 Ghi nhớ:
Tóm lại, để có thể build và chạy chương trình thành công, chúng ta sẽ phải: Embed RPATH vào trong binary để dyld biết phải tìm .dylib ở đâu. Trong ví dụ này, chúng ta sẽ cần trỏ đến /opt/onnxruntime/lib
7. Giải pháp 1: Fix trong quá trình build
7.1 Sử dụng biến môi trường (idiomatic CGO)
Đối với hệ thống lớn, chúng ta sẽ ưu tiên sử dụng biến môi trường khi compile:
CGO_CFLAGS→ headerCGO_LDFLAGS→ linker + rpath
Điểm mạnh của việc sử dụng biến môi trường là tránh khỏi việc sử dụng quoting nesting (gặp phải ở giải pháp thứ 2)
Giờ, chúng ta sẽ embed trực tiếp RPATH bằng linker flag Wl,-rpath,/path
Wl: sẽ giúp pass các flag vào thẳng linker (https://man7.org/linux/man-pages/man1/ld.1.html)rpath: tạo LC_RPATH trong Mach-O
Lệnh build của chúng ta như sau:
CGO_CFLAGS=-I/opt/onnxruntime/include \
CGO_LDFLAGS="-L/opt/onnxruntime/lib -Wl,-rpath,/opt/onnxruntime/lib" \
CGO_ENABLED=1 \
go build -tags onnxruntime -ldflags="-linkmode=external" -o run_example github.com/google/magika/go/example
Chương trình giờ đây đã chạy thành công
./run_example
{Label:txt MimeType:text/plain Group:text Description:Generic text document Extensions:[txt] IsText:true}
7.2 Command line (không qua env)
Ngoài việc sử dụng biến môi trường, chúng ta cũng có thể thêm trực tiếp flag cho linker vào command.
⚠️ Lưu ý:
Để có thể nhét nhiều flag vào một extldflags duy nhất, chúng ta sẽ cần phải bọc -extldflags= trong 2 dấu nháy đơn (https://github.com/golang/go/issues/6234#issuecomment-66084513)
Ví dụ:
'-extldflags=A B C D' → ✅ Chính xác
-extldflags='A B C D' → ❌ Không chính xác
Command cần chạy sẽ là:
CGO_CFLAGS=-I/opt/onnxruntime/include \
CGO_ENABLED=1 \
go build -tags onnxruntime -ldflags="-linkmode=external '-extldflags=-L/opt/onnxruntime/lib -Wl,-rpath,/opt/onnxruntime/lib'" -o run_example github.com/google/magika/go/example
8. Giải pháp 2: Sửa binary đã build (post-link)
Chúng ta có thể trực tiếp embed RPATH vào trong binary:
install_name_tool -add_rpath /opt/onnxruntime/lib run_example
Công cụ này thêm LC_RPATH vào Mach-O header của binary. Cách này hay được sử dụng trong quá trình debug.
9. Giải pháp 3: Tạo portable binary
Chúng ta có thể bundle thư viện cùng với binary để tạo 1 bản portable.
bundle/
├──run_example
└──lib/
├── libonnxruntime.dylib
└── ...
RPATH của binary sẽ cần trỏ đến @executable_path/lib . Do vậy, flag mà cần pass đến linker sẽ là
-Wl,-rpath,@executable_path/lib
Do vậy, lệnh build cuối cùng sẽ là:
CGO_CFLAGS=-I/opt/onnxruntime/include \
CGO_LDFLAGS="-L/opt/onnxruntime/lib -Wl,-rpath,@executable_path/lib" \
CGO_ENABLED=1 \
go build -tags onnxruntime -ldflags="-linkmode=external" -o run_example github.com/google/magika/go/example
Ngoài ra, trong trường hợp này, chúng ta cũng có thể sử dụng @loader_path thay cho @executable_path
10. Vài lưu ý
Khi build CGO trên macOS, hãy luôn nghĩ theo 3 giai đoạn: Compile → Link → Runtime
| Giai đoạn | Công cụ | Cần gì |
|---|---|---|
| Compile | clang | header (CGO_CFLAGS) |
| Link | ld | lib path (-L, -linkmode=external) |
| Runtime | dyld | RPATH (LC_RPATH) |
Phụ lục
1. @rpath, @loader_path, @executable_path
macOS cung cấp 3 placeholder quan trọng trong Mach-O:
1.1 @executable_path – thư mục của binary chính
Đây là placeholder rất hay dùng để bundle binary: @executable_path/lib/abc.dylib
Điều này cho phép có thể gọi chương trình từ bất cứ đâu miễn là cấu trúc thư mục của chương trình và thư viện kèm theo không thay đổi.
Ví dụ: chúng ta có cấu trúc thư mục:
example
├── lib
│ ├── abc.c
└── main.c
Chúng ta sẽ chạy 2 lệnh sau để tạo thư viện libabc.dylib nằm trong thư mục example/lib và compile chương trình
clang -dynamiclib ./lib/abc.c -o ./lib/libabc.dylib -install_name @
executable_path/lib/libabc.dylib
clang -L./lib -labc main.c -o main
Trong đó:
-install_name: flag để cấu hình install ID cho thư viện-L: search path cho thư viện-l: tên của dylib sẽ được link đến (chú ý: không cần đầy đủ đầulibvà đuôidylib)
Kết quả mong đợi: binary main sẽ cần phải:
- chạy được từ bất kỳ mọi nơi:
cd ~ ./main cd ~/Desktop ./main cd ~/abc/abc/abc ./main - khi chúng ta kiểm tra với
otool -L mainsẽ cần ra kết quả sau:main: @executable_path/lib/libabc.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)
Trong trường hợp nếu chúng ta ko compile với @executable_path : clang -dynamiclib ./lib/abc.c -o ./lib/libabc.dylib
Khi chạy binary main ở một nơi khác, chúng ta sẽ gặp lỗi
dyld[46698]: Library not loaded: ./lib/libabc.dylib
Referenced from: <651A1B17-2BAD-39FC-9556-21509304D659> <path đến main>
Reason: tried: './lib/libabc.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS./lib/libabc.dylib' (no such file), './lib/libabc.dylib' (no such file)
[1] 46698 abort <path đến main>
Để có thể chạy được binary main từ bất cứ đâu, chúng ta sẽ phải sửa lại install ID cho binary main về đúng cấu trúc thư mục
install_name_tool -change ./lib/libabc.dylib @executable_path/lib/libabc.dylib main
1.2 @loader_path – thư mục của thư viện đang load
@loader_path trỏ tới thư mục chứa file .dylib đang được load
Rất hữu ích khi các thư viện phụ thuộc lẫn nhau
Ví dụ: đối với main → abc.dylib → def.dylib, khi chúng ta đang load abc.dylib, @loader_path sẽ trỏ đến thư mục chứa abc.dylib
1.3 @rpath – runtime search path
@rpathkhông trỏ trực tiếp tới thư mục nào, mà được resolve bằng LC_RPATH trong binary- Binary có thể chứa nhiều LC_RPATH (https://itwenty.me/posts/01-understanding-rpath/#what-is-rpath)
Ví dụ: đối với @rpath/libonnxruntime.dylib, dyld sẽ thử cho đến khi tìm đúng path
LC_RPATH[0] + libonnxruntime.dylib
LC_RPATH[1] + libonnxruntime.dylib
...
2. Debug CGO + dylib như thế nào cho hiệu quả
Khi gặp lỗi CGO trên macOS, chúng ta có thể kiểm tra theo checklist:
| Vấn đề cần kiểm tra | Command | Ý nghĩa |
|---|---|---|
| Binary đang link tới cái gì? | otool -L binary |
Kiểm tra các thư viện gây ra vấn đề |
| Binary có RPATH không? | otool -l binary | grep -A2 LC_RPATH |
Kiểm tra LC_RPATH có chưa, cũng như đang trỏ đến vị trí nào |
| Thư viện có tồn tại ở đó không? | ls /opt/onnxruntime/lib |
|
| Thư viện đó có phụ thuộc thêm lib khác không? | otool -L libonnxruntime.dylib |
Cũng có thể vấn đề có thể xảy ra ở giữa chuỗi các thư viện |
Tham khảo thêm
Ngoài ra, bạn đọc quan tâm để khám phá thêm về các phần được giới thiệu trong nội dung này cũng có thể đọc thêm các nội dung được bao gồm dưới đây
- Tài liệu sử dụng flag -Wl và -rpath khi compile với gcc: https://man7.org/linux/man-pages/man1/ld.1.html
- GCC và Rpath: https://www.bourguet.org/v2/cpplang/gcc-rpath
- Tạo thư viện runpath: Xem tại đây
- Giải thích về rpath: https://blog.krzyzanowskim.com/2018/12/05/rpath-what/
- Giải thích về @executable_path, @loader_path and @rpath: https://itwenty.me/posts/01-understanding-rpath/
- Tài liệu về Runtime Protections của SIP: Xem tại đây
