Standart C Fonksiyonlarını Override Etmek

Merhaba arkadaşlar,

Bu aralar doğru olan herşeyi bozma eğilimindeyken bu sefer de size standart C fonksiyonlarını nasıl override edebileceğinizi göstereyim.Öncelikle şuradaki yazımı okumadıysanız ilk olarak onu okumanızı öneririm;http://ahmetkotan.com.tr/python-super-methodu-ve-django-kullanimi.html.

C'de bir fonksiyonu override etmek için önce bir shared object(.so) oluşturup sonrada bu shared object'i kendi yazdığımız C uygulamamızda preload olarak çağırıp çalıştıracaz.En sonda preload olarak çağırmak yerine kendi C uygulamamızı bu shared object ile compile edip kullanacaz.

Öncelikle basit bir C uygulaması yazıp derleyelim ve çalıştıralım.

// test.c
#include <stdio.h>
#include <stdlib.h>

int main(){ char *str = (char*) malloc(50); gets(str); puts(str); return 0; }

Bu basit C kodunu standart bir compile komutu ile derleyip çalıştıralım.
$ gcc test.c -o test
Yaptığı işlem çok basit zaten, ilk açılışta bir string istiyor sizden ve sonra bu stringi ekrana yazıyor tekrardan.Şimdi burada kullandığımız iki fonksiyon gets() ve puts()'u override edip preload olarak vererek çalıştıracaz.

Öncelikle preloader olarak kullanacağımız C dosyamızı oluşturalım;

// preload.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
Standart C uygulamalarında ki gibi main() veya benzeri bir fonksiyona ihtiyaç duymuyoruz.Kısaca bu ufaklıklarıda açıklayacak olursak;

  • #define _GNU_SOURCE içinde GNU için gerekli olan değişkenleri barındırıyor.Bunlardan birini birazdan kullanacağımız için tanımladım.
  • #include <dlfcn.h> Kullanacağımız en önemli header.Fonksiyonların orjinal haline ulaşmak için bu header'ın bize sağladığı bir fonksiyonu kullanacağımız için tanımladım.
  • #include <stdlib.h> El alışkanlığı, ne zaman pointer kullanacak olsam tanımlarım :) Bir defasında bir problem yaşadığımı hatırlıyorum çünkü, o gün bugündür sürekli tanımlıyorum :/
  • #include <string.h> Ufak bir string fonksiyonu kullanacam birazdan o yüzden.

İlk olarak gets() fonksiyonuyla başlayalım.Kullanımına $ man gets komutu ile bakabiliriz. SYNOPSIS sekmesinde char *gets(char *s) şeklinde bir kullanımı olduğunu görebilirsiniz.Kısacası argüman olarak bir char pointer alıyor ve yine bir char pointer döndürüyor.preload.c dosyamız içinde şöyle bir tanımlama ile başlayabiliriz;

char *gets(char *s){
    static char* (*p_gets)(char *s) = NULL;
    r_gets = dlsym(RTLD_NEXT, "gets");
    r_gets(s);
}
Satırları kısaca açıklayacak olursak; Öncelikle man komutu ile gets() fonksiyonuna bakınca edindiğimiz bilgilerden yola çıkarak fonksiyonumuzu tanımlıyoruz.Bir char pointer döndürecek ve argüman olarak bir char pointer alacak şekilde tanımlıyoruz.Daha sonra gerçek gets() fonksiyonuna erişmek için bir fonksiyon adresine ihtiyacımız var ve ilk başta static olarak NULL değeri verip bu değişkeni tanımlıyoruz.Direk olarak dlsym() dönüşüne tanımlarsanız initiliaze hatası veriyor.Bir sonraki adımda da dlsym() fonksiyonu ile gerçek gets() fonksiyonuna erişiyoruz ve en altta da bu fonksiyonu çalıştırıyoruz.Buraya kadar herşey normal daha önceki bu yazımda anlattığım super() fonksiyonunun yaptığı işlemi yapmış olduk.Bu şekilde derleyip çalıştırırsanız klasik bir gets() fonksiyonu görevi görecektir.Birkaç basit ekleme ile bu fonksiyonu biraz değiştirebiliriz;
char *gets(char *s){
    printf(">> ");
    static char* (*p_gets)(char *) = NULL;
    r_gets = dlsym(RTLD_NEXT, "gets");
    r_gets(s);
}
gibi.Artık gets() fonksiyonu her çalıştığında ekranda ">> " işaretini göreceksiniz.

puts() fonksiyonu içinde aynısını yapalım.Öncelikle kullanımına bakalım $ man puts komutu ile. Yine SYNOPSIS sekmesine bakıp int puts(const char *s) şeklinde bir kullanımı olduğunu görebiliriz.Bu da yine bir char pointer alıp integer değer döndürüyor bize.Fonksiyon tanımlamamız;

int puts(const char *s){
    static int (*r_puts)(const char *) = NULL;
    r_puts = dlsym(RTLD_NEXT, "puts");
    r_puts(s);
}
Daha sonra birkaç ufak ekleme yapalım;
int puts(const char *s){
    printf("Length:%d, Value: ", strlen(s));
    static int (*r_puts)(const char *) = NULL;
    r_puts = dlsym(RTLD_NEXT, "puts");
    r_puts(s);
}
Şimdide puts() fonksiyonu her çalıştığında ekrana bu fonksiyona verdiğimiz stringin uzunluğu ve sonra da değeri yazılacak.Örnek olarak "ahmetkotan" değerini verirsek ekranda "Length: 10, Value: ahmetkotan" şeklinde bir çıktı alacağız.

preload.c dosyamızın son haline bakarsak;

// preload.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>

char *gets(char *s){ printf(">> "); static char* (*p_gets)(char *) = NULL; r_gets = dlsym(RTLD_NEXT, "gets"); r_gets(s); }

int puts(const char *s){ printf("Length:%d, Value: ", strlen(s)); static int (*r_puts)(const char *) = NULL; r_puts = dlsym(RTLD_NEXT, "puts"); r_puts(s); }

Şimdi bu preload.c dosyamızı derleyip shared object haline getirelim.
$ gcc -fPIC -m32 -shared -o preload.so preload.c -l dl
Parametreleri tek tek açıklayacak olursak;

  • -fPIC Shared Object uygulamaları hazırlanırken fonksiyonların daha verimli kullanılması için verilen bir parametre.
  • -m32 Ben 32-bit bir OS'ta çalıştığım için 32-bit olarak derlememe yardımcı oluyor.OS'lara göre -m64, -m elf_i386, -m elf_x86, -m elf_x86_64 şeklinde kullanımları da var.Kendinize göre bir parametre seçip kullanabilirsiniz.
  • -shared Shared Object oluşturucaz, bir zahmet belirtelim :)
  • -o preload.so Output dosyamızı belirtiyoruz.
  • preload.c Source dosyamız.
  • -l dl Daha önce dışardan bir header ile C derleyenler denk gelmiştir buna.Bende Raspberry Pi'da wiringPi ile derleme yaparken bunun gibi bir parametre kullanmıştım.dlfcn.h headerını ve dolayısıyla da dynamic library'leri kullandığımız için(libdl.so) dl argümanı ile çalıştırıyoruz.Belirtmeden geçemeyeceğim -ldl şeklinde kullanımı daha yaygındır.

Şimdi shared object'imizi de hazırladığımıza göre test.c'den derlediğimiz test uygulamamızı bu preloader ile çalıştıralım.
$ LD_PRELOAD=./preload.so ./test
Gördüğünüz gibi az önce orjinal fonksiyonlarda yaptığımız değişiklikler burada görünüyor.Fakat LD_PRELOAD variable'ını vermeden ./test şeklinde çalıştırırsak yine eskisi gibi çalışacak uygulamamız.Son olarakta test uygulamamızı kendi oluşturduğumuz shared object ile derleyip her platformda kullanılabilir hale getirelim.
$ gcc -Wall -Werror -o test test.c ./preload.so -ldl
Parametreleri açıklayacak olursak;

  • -Wall -Werror Compile sırasındaki bütün warning ve error'ları ekrana bastırabilmek için.
  • -o test Output dosyamız.
  • test.c Source dosyamız.
  • ./preload.so Shared object'imiz.Burada ya full path vermek gerekiyor ya da "./" şeklinde bir kullanım ile kendi bulunduğunuz dizini belirtmeniz gerekiyor.
  • -ldl Dynamic Library'leri dahil etmek için.

Şimdi ./test şeklinde çalıştırdığımız da şu şekilde bir ekran alabiliriz.

>> ahmetkotan
Length: 10, Value:
ahmetkotan

Canlıda görelim hocam;

En altta da test uygulamamızın kullandığı dynamic library'lerde kendi yazdığımız preload.so objemizi görebiliriz.

Umarım faydalı olmuştur,
İyi çalışmalar..


Tags: standart c, libc, libdl, dynamic libraries, shared object, preload, gets function, puts function