Trang chủHướng dẫnĐóng gói Magika (CGO + ONNX Runtime) trên macOS
Chuyên gia
Go

Đóng gói Magika (CGO + ONNX Runtime) trên macOS

CyStack blog 9 phút để đọc
CyStack blog23/12/2025
Locker Avatar

Dương Trần

Tech enthusiast on a lifelong quest to break, build, and secure cool stuff. Known in the team as the go-to rubber duck 🦆.
Locker logo social
Reading Time: 9 minutes

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 macOSWindows
  • 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”

Nội dung hình ảnh máy tính có dòng chữ "Hướng dẫn đóng gói Magika qua CGO trên macOS"

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ó onnxruntime trong thư mục /opt/onnxruntime

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:

  1. tiết kiệm bộ nhớ
  2. dễ dàng cập nhật
  3. tái sử dụng code trên nhiều ứng dụng.
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

ONNX Runtime giúp hỗ trợ việc đóng gói Magika trên macOS với Go

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

Linker cần biết:

  • thư viện nằm ở đâu (ví dụ: .dylib) → cần L/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_PATH trê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

  1. Trước hết, hãy đảm bảo đã tải ONNXRuntime về máy.
    1. 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
      
    2. 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/
      
    3. Chúng ta sẽ thấy có 2 folder includelib trong /opt/onnxruntime/
      1. folder include bao 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.
      2. folder lib bao gồm các thư viện động được binary sử dụng lúc runtime
  2. Trong file go/example/main.go , chúng ta sẽ cần đảm bảo assetsDir đã 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, dyld thấy @rpath/... nhưng lại không có LC_RPATH

 

4. Hiểu đúng @rpath trên macOS

 

Trên macOS (Mach-O):

  • .dylib thườ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

 

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 → header
  • CGO_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

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)

Magika sử dụng CGO để triển khai đóng gói tệp trên macOSMagika sử dụng CGO để triển khai đóng gói tệp trên macOS

 

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 đủ đầu lib và đuôi dylib)

Kết quả mong đợi: binary main sẽ cần phải:

  1. chạy được từ bất kỳ mọi nơi:
    cd ~
    ./main
    cd ~/Desktop
    ./main
    cd ~/abc/abc/abc
    ./main
    
  2. khi chúng ta kiểm tra với otool -L main sẽ 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_pathclang -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

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

  1. 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
  2. GCC và Rpath: https://www.bourguet.org/v2/cpplang/gcc-rpath
  3. Tạo thư viện runpath: Xem tại đây
  4. Giải thích về rpath: https://blog.krzyzanowskim.com/2018/12/05/rpath-what/
  5. Giải thích về @executable_path, @loader_path and @rpath: https://itwenty.me/posts/01-understanding-rpath/
  6. Tài liệu về Runtime Protections của SIP: Xem tại đây 

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất