> "long jump" : Anımsanacağı üzere "goto" ifadeleri ile programın akışının bir noktadan bir noktaya geçmesini mümkün kılabilmekteyiz. Ancak C standartlarına göre "goto" deyiminin aynı fonksiyon içerisinde bulunması gerekmektedir. * Örnek 1, #include int foo() { goto MY_EXIT; // error: label ‘MY_EXIT’ used but not defined } int main(void) { /* # OUTPUT # Waiting for signals... Signal-10 occured!.. */ foo(); MY_EXIT: ; return 0; } Dolayısıyla "goto" ifadeleriyle fonksiyonun dışındaki bir alana programın akışı geçiremiyoruz. Çünkü, -> "goto" çağrısının yapıldığı fonksiyondaki yerel değişkenlerin, "goto" çağrısından sonraki durumları. -> "goto" çağrısı ile gidilen fonksiyondaki "return" çağrısı ile nereye geri dönülecektir? gibi problemleri de beraberinde getirecektir. İşte akışı fonksiyon dışına geçirmemize olan tanıyan mekanizmaya ise "long jump" denmektedir. Fakat bu mekanizmanın kullanımı da sınırlıdır. Programın akışının daha önce geçmiş olduğu bir noktaya "long jump" yapabiliyoruz, her ne kadar ismi tam olarak bu anlamı veremiyor olsada. Yani teknik olarak geçmişe gidiyoruz. C dilinde "long jump" işlemi ise iki adet Standart C fonksiyonu ile yapılmaktadır. Bunlar "setjmp" ve "longjmp" isimli fonksiyonlardır. >> "setjmp" ve "longjmp": Fonksiyonların prototipleri aşağıdaki gibidir; #include int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); "setjmp" ile geri dönmek istediğimiz noktayı belirliyor, "longjmp" ile de o noktaya tekrar dönüyoruz. Eğer "setjmp" çağrısı ilk kez çağrılmışsa "0", "longjmp" vasıtasıyla çağrılmışsa da "longjmp" ın ikinci parametresine geçtiğimiz değerle geri dönmektedir. Fonksiyonların birinci parametresi ise "jmp_buf" yapı türündendir. Bizler bu yapı türünden bir nesneyi argüman olarak fonksiyonlara geçmeliyiz. Eğer "setjmp" çağrısının geri dönüş değerini kontrol etmezsek, sonsuz döngüye gireceğiz. * Örnek 1, Sonsuz Döngü: #include #include jmp_buf g_checkPoint; void baz() { printf("baz()\n"); longjmp(g_checkPoint, 31); } void bar() { printf("bar()\n"); baz(); } void foo() { printf("foo()\n"); setjmp(g_checkPoint); bar(); } int main(int argc, char** argv) { /* # OUTPUT # foo() bar() baz() bar() baz() bar() baz() bar() baz() bar() baz() ... */ foo(); return 0; } * Örnek 2, Normal kullanım: #include #include jmp_buf g_checkPoint; void baz() { printf("baz()\n"); longjmp(g_checkPoint, 31); } void bar() { printf("bar()\n"); baz(); } void foo() { printf("foo()\n"); int jump_result; if ((jump_result = setjmp(g_checkPoint)) == 0) bar(); else printf("Came from the past: %d\n", jump_result); } int main(int argc, char** argv) { /* # OUTPUT # foo() bar() baz() Came from the past: 31 */ foo(); return 0; } Öte yandan "setjmp" ve "longjmp" isimli fonksiyonlar, ilgili prosesin "signal mask" kümesinin korunacağını garanti ETMEMEKTEDİR. Yani "setjmp" çağrısı sırasında prosesin sinyal bloke kümesinin de kayıt altına alınacağı, "longjmp" çağrısı ile tekrar geri yükleneceği GARANTİ EDİLMEMİŞTİR. İşte bu garantiyi sağlayanlar "sigsetjmp" ve "siglongjmp" isimli fonksiyonlardır. Bir diğer deyişle bu iki fonksiyon, evvelki fonksiyonların sinyalli versiyonlarıdır. >> "sigsetjmp" ve "siglongjmp" : Fonksiyonların prototipleri aşağıdaki gibidir; #include int sigsetjmp(sigjmp_buf env, int savesigs); void siglongjmp(sigjmp_buf env, int val); Fonksiyonların kullanım biçimileri "setjmp" ve "longjmp" fonksiyonları ile benzerdir. Tek fark "sigsetjmp" iki parametre almasıdır ki iş bu ikinci parametre prosesin sinyal kümesinin kayıt altına alınıp alınmayacağını belirtir; bu parametreye "0" geçilirse ilgili sinyal bloke kümesi dikkate alınmaz, "0" dışı ise dikkate alınır. Pekiyi bizler "long jump" işlevine tam olarak hangi senaryolarda gereksinim duyarız? Örneğin, oluşan sinyalden dolayı sonsuz döngüye girmemiz durumunda, yine bu mekanizma ile daha güvenli bir yere atlayabiliriz. Fakat "long jump" ile gittiğimiz yerde hala "asenkron sinyal güvenli" fonksiyonları kullanmak zorundayız. Bu mekanizma, bu konuda bize bir güvence SAĞLAMAMAKTADIR. * Örnek 1.0, "SIGSEGV" sinyalinin oluşması, "sigsegv_handler" tekrar tekrar çağrılmasına sebebiyet verecektir. #include #include #include #include void sigsegv_handler(int sig); void exit_sys(const char* msg); int main(void) { /* # OUTPUT # SIGSEGV handler SIGSEGV handler SIGSEGV handler SIGSEGV handler ... */ struct sigaction sa; sa.sa_handler = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // "SA_RESTART" bayrağından dolayı yeniden başlatılacaktır. if(sigaction(SIGSEGV, &sa, NULL) == -1) exit_sys("sigaction"); char* ptr = (char*)0x123456789; // Programımız için tahsis edilmemiş bir alana erişimden kaynaklı // sinyal gönderilmiştir. putchar(*ptr); return 0; } void sigsegv_handler(int sig) { printf("SIGSEGV handler\n"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 1.1, Dolayısıyla bundan kurtulmak için "exit" fonksiyon çağrısını, "sigsegv_handler" içerisinde yapabiliriz. #include #include #include #include void sigsegv_handler(int sig); void exit_sys(const char* msg); int main(void) { /* # OUTPUT # SIGSEGV handler */ struct sigaction sa; sa.sa_handler = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // "SA_RESTART" bayrağından dolayı yeniden başlatılacaktır. if(sigaction(SIGSEGV, &sa, NULL) == -1) exit_sys("sigaction"); char* ptr = (char*)0x123456789; // Programımız için tahsis edilmemiş bir alana erişimden kaynaklı // sinyal gönderilmiştir. putchar(*ptr); return 0; } void sigsegv_handler(int sig) { printf("SIGSEGV handler\n"); exit(EXIT_FAILURE); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 1.2, Bir diğer alternatif ise daha güvenli bir noktaya "long jump" yapmaktır. #include #include #include #include #include void sigsegv_handler(int sig); void exit_sys(const char* msg); jmp_buf g_jbuf; int main(void) { /* # OUTPUT # SIGSEGV handler */ struct sigaction sa; sa.sa_handler = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // "SA_RESTART" bayrağından dolayı yeniden başlatılacaktır. if(sigaction(SIGSEGV, &sa, NULL) == -1) exit_sys("sigaction"); char* ptr = (char*)0x123456789; if (sigsetjmp(g_jbuf, 1) == 31) { printf("SIGSEGV handler\n"); //... } else { // Programımız için tahsis edilmemiş bir alana erişimden kaynaklı // sinyal gönderilmiştir. putchar(*ptr); } return 0; } void sigsegv_handler(int sig) { //... siglongjmp(g_jbuf, 31); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Diğer taraftan bazı sinyallerin, örneğin "SIGSEGV" sinyali, varsayılan davranışı prosesi sonlandırmak ve bir "core" dosyası oluşturmaktadır. Böyle sinyaller terminal ekranında "core dumped" yazısının çıkmasına da neden olur. >> "core" dosyası: Bu dosyanın üretilme amacı, bu dosyanın "debugger" tarafından incelenmesini sağlatmaktır. Böylelikle programın nerede ve nasıl çöktüğüyle alakalı bilgi alabiliriz. Tabii bu dosyanın üretilmesi hususunda da bir takım sınırlamalar mevcuttur. Linux sistemleri için bahsedecek olursak; "ulimit -a" komutu ile sorgulama yaptığımız zaman "core file size" değerinin "0" OLMAMASI gerekmektedir. Pekala aynı sorgulamayı, "ulimit -c" komutu ile de gerçekleştirebiliriz. "core file size" değerinin "unlimited" hale getirilmesi için, "ulimit -c unlimited" komutunun çalıştırılması gerekmektedir. Yine iş bu "core" dosyalarının isimlendirme kurallarını görmek için, "/proc/sys/kernel" dizini içerisindeki "core_pattern" isimli dosyaya bakmalıyız. Bu dosya içerisinde bir "core" dosyasının hangi isimle ve nerede oluşturulacağı bilgisi belirtilmiştir. Tipik olarak aşağıdaki gibi bilgiler yer almaktadır, iş bu dosyada; "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e" Buradan hareketle; -> "systemd-coredump" : "core" dosyasını oluşturan program. "/usr/lib/systemd" dizini içerisinde yer alır. -> "%P" : PID of dumped process, as seen in the initial PID namespace (since Linux 3.12). -> %u : Numeric real UID of dumped process. -> %g : Numeric real GID of dumped process. -> %s : Number of signal causing dump. -> %t : Time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). -> %c : Core file size soft resource limit of crashing process (since Linux 2.6.24). -> ... : (see "https://man7.org/linux/man-pages/man5/core.5.html" for more information.) Pekiyi bizler "core" dosyalarını nasıl görüntüleyebiliriz? İş bu dosyalar genellikle ".lz4" formatında, yani sıkıştırılmış biçimde, "/var/lib/systemd/coredump/" dizininde oluşturulurlar. "debugger" programlar sıkıştırılmış bu dosyaları yükleyemediklerinden, ilk olarak bu dosyaları "coredumpctl" isimli program vasıtasıyla açıyoruz. Eğer bu program sistemimizde yüklü değilse, "sudo apt install systemd-coredump" komutuyula sistemimize yükleyebiliriz. Daha sonra, "coredumpctl list" komutuyla da sistemimizdeki bütün "core" dosyalarını görüntüleyebiliriz. Daha sonra "coredumpctl gdb" komutuyla da en son oluşturulan "core" dosyasının detaylarını görüntüleyebiliriz.