main.go
Decidí escribir este post luego de trabajar en una nueva herramienta para go-shield donde intentaba leer la memoria de un proceso en ejecución. Me di cuenta de que Go no tiene una forma directa de hacerlo, pero C sí. Así que decidí integrar código en C en mi proyecto de Go.
El uso de C en tu código de Go puede ser beneficioso por varias razones:
Para integrar código en C en tu proyecto de Go, puedes seguir estos pasos:
Crear un archivo de código en C: Crea un archivo con extensión .c
que contenga tu código en C.
~int add(int a, int b) { return a + b; }
Crear un archivo de cabecera en C: Crea un archivo con extensión .h
que contenga las declaraciones de tus funciones en C.
~#ifndef MYLIB_H #define MYLIB_H int add(int a, int b); #endif
Compilar el código en C: Compila tu código en C en una biblioteca compartida (.so
en Linux o .dll
en Windows).
~gcc -c -fPIC mylib.c -o mylib.o gcc -shared -o libmylib.so mylib.o
Crear un archivo de código en Go: Crea un archivo con extensión .go
que importe la biblioteca compartida y haga uso de las funciones en C.
~package main // #cgo CFLAGS: -I. // #cgo LDFLAGS: -L. -lmylib // #include "mylib.h" import "C" import "fmt" func main() { a := C.add(1, 2) fmt.Println("add from c:", a) }
Incluir el proceso de compilación en tu Makefile: Asegúrate de incluir el proceso de compilación de tu código en C en tu Makefile.
~build: gcc -c -fPIC c_code.c -o c_code.o gcc -shared -o libc_code.so c_code.o go build -o main main.go
main.go
mylib.c
mylib.h
Makefile
Otra forma de integrar código en C en tu proyecto de Go es escribir el código en C directamente en tu archivo de Go. Puedes hacerlo utilizando un comentario de bloque justo antes de importar la librería de Go llamada "C".
~package main /* #include <stdio.h> #include <stdlib.h> void printHello(const char *name) { printf("Hello, %s!", name); fflush(stdout); } int add(int a, int b) { return a + b; } char *reserveCharMemory(int size, char *ptr) { ptr = (char *)malloc(size); if (ptr == NULL) { printf("Memory allocation failed"); fflush(stdout); } // Print the memory address printf("Memory address [C]: %p", ptr); fflush(stdout); // Some garbage value in the memory printf("Value: %c", *ptr); fflush(stdout); return ptr; } void releaseCharMemory(char *ptr) { free(ptr); } */ import "C" import ( "fmt" ) func main() { C.printHello(C.CString("Carlos")) a := C.add(1, 2) fmt.Printf("add from c:%d", a) // Memory allocation in C var ptr *C.char ptr = C.reserveCharMemory(100, ptr) if ptr == nil { fmt.Println("Memory allocation failed") } defer C.releaseCharMemory(ptr) *ptr = 'C' fmt.Printf("Memory address [Go]: %p", ptr) fmt.Printf("Value: %c", *ptr) }
$ Hello, Carlos! $ add from c:3 $ Memory address [C]: 0x60000370c000 $ Value: $ Memory address [Go]: 0x60000370c000 $ Value: C
En mi caso en particular lo use para lograr acceder a la librería ptrace de C para leer la memoria de un proceso en ejecución. Para luego almacenarlo en un archivo bin que podía procesar usando otras herramientas del sistema. Este es el proyecto donde recurrí al uso de C en Go: go-memory-dumper.