c विराम के दौरान विचित्र व्यवहार() सिस्टम कॉल




linux debugging (3)

Glibc में (जो कि libc डेबियन का उपयोग करता है) abort फ़ंक्शन (यह सिस्टम कॉल नहीं है, यह सामान्य फ़ंक्शन है) इस प्रकार घोषित किया गया है:

extern void abort (void) __THROW __attribute__ ((__noreturn__));

यह बिट: __attribute__ ((__noreturn__)) एक जीसीसी एक्सटेंशन है जो बताता है कि फ़ंक्शन वापस नहीं हो सकता। आपका आवरण समारोह वापस करता है जो संकलक को उम्मीद नहीं करता था। इसकी वजह से यह दुर्घटनाग्रस्त हो या पूरी तरह अप्रत्याशित हो।

संकलित होने पर आपका कोड कॉल करने के लिए stdlib.h से घोषणाओं का उपयोग कर रहा होगा, जो आपके द्वारा लिंकर को दिया गया झंडे वह नहीं बदलेगा।

नोरटरन फ़ंक्शंस को अलग तरीके से कहा जाता है, संकलक को रजिस्टरों को संरक्षित करने की ज़रूरत नहीं है, यह उचित कॉल करने के बजाय फ़ंक्शन में कूद सकता है, यह उसके बाद भी कोई भी कोड उत्पन्न नहीं कर सकता क्योंकि यह कोड परिभाषा के द्वारा नहीं पहुंच योग्य है।

यहां एक सरल उदाहरण है:

extern void ret(void);
extern void noret(void) __attribute__((__noreturn__));

void
foo(void)
{
    ret();
    noret();
    ret();
    ret();
}

कोडांतरक में संकलित (यहां तक ​​कि अनुकूलन के बिना):

$ cc -S foo.c
$ cat foo.s
[...]
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    call    ret
    call    noret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-4)"
    .section    .note.GNU-stack,"",@progbits

ध्यान दें कि noret लिए कॉल है, लेकिन इसके बाद कोई भी कोड नहीं है। ret करने के लिए दो कॉल उत्पन्न नहीं हुए थे और कोई ret अनुदेश नहीं है। फ़ंक्शन सिर्फ समाप्त होता है इसका मतलब यह है कि यदि फ़ंक्शन noret वास्तव में एक बग के कारण देता है (जो आपके noret कार्यान्वयन है), कुछ भी हो सकता है इस मामले में हम हमारे द्वारा कोड सेगमेंट में जो कुछ भी होता है उसे निष्पादित करते रहेंगे। हो सकता है कि किसी अन्य फ़ंक्शन, या कुछ स्ट्रिंग्स, या सिर्फ शून्य, या शायद हम भाग्यशाली हैं और मेमोरी मैपिंग इसके ठीक बाद ही समाप्त हो जाती है।

वास्तव में, कुछ बुराई करते हैं कभी वास्तविक कोड में ऐसा न करें यदि आप कभी भी सोचते हैं कि यह एक अच्छा विचार है तो आपको अपने कंप्यूटर पर कुंजी को सौंपने की ज़रूरत होगी और अपना हाथ रखते हुए धीरे-धीरे कीबोर्ड से दूर रहें:

$ cat foo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void __wrap_abort(void)
{
    printf("=== Abort called !=== \n");
}

int
main(int argc, char **argv)
{
    abort();
    return 0;
}

void
evil(void)
{
    printf("evil\n");
    _exit(17);
}
$ gcc -Wl,--wrap=abort -o foo foo.c && ./foo
=== Abort called !===
evil
$ echo $?
17

जैसा कि मैंने सोचा था कि कोड main रूप में रखा गया है और इस सरल उदाहरण में संकलक को यह नहीं समझा गया कि यह फ़ंक्शन पुन: संयोजन करने के लिए एक अच्छा विचार होगा।

मुझे एकांतिक परीक्षण लिखने के लिए, abort () सिस्टम कॉल को लपेटने की आवश्यकता है I

यहां कोड का एक स्निपेट है:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

extern void __real_abort(void);
extern void * __real_malloc(int c);
extern void __real_free(void *);


void __wrap_abort(void)
{
    printf("=== Abort called !=== \n");
}   

void * __wrap_malloc(int s)
{
    void *p = __real_malloc(s);
    printf("allocated %d bytes @%p\n",s, (void *)p);
    return p;
}

void __wrap_free(void *p)
{
    printf("freeing @%p\n",(void *)p);
    return __real_free((void *)p);
}


int main(int ac, char **av)
{
    char *p = NULL;
    printf("pre malloc: p=%p\n",p);
    p = malloc(40);
    printf("post malloc p=%p\n",p);

    printf("pre abort\n");
    //abort();
    printf("post abort\n");

    printf("pre free\n");
    free(p);
    printf("post free\n");
    return -1;
}

फिर मैं इसे निम्न कमांड लाइन का उपयोग करके संकलित करता हूं:

gcc -Wl,--wrap=abort,--wrap=free,--wrap=malloc -ggdb -o test test.c

इसे चलाने से निम्नलिखित आउटपुट मिलता है:

$ ./test
pre malloc: p=(nil)
allocated 40 bytes @0xd06010
post malloc p=0xd06010
pre abort
post abort
pre free
freeing @0xd06010
post free

तो सब ठीक है। अब चलो एक ही कोड का परीक्षण करें लेकिन abort () कॉल uncommented के साथ:

$ ./test
pre malloc: p=(nil)
allocated 40 bytes @0x1bf2010
post malloc p=0x1bf2010
pre abort
=== Abort called !=== 
Segmentation fault (core dumped)

मुझे सच में समझ में नहीं आ रहा है कि क्यों मुझे एक विभाजन त्रुटि मिलती है जबकि abort () syscall मजाक ... हर सलाह का स्वागत है!

मैं एक x86_64 कर्नेल पर डेबियन जीएनयू / लिनक्स 8.5 चलाता हूं। मशीन एक कोर i7 आधारित लैपटॉप है


विधानसभा उत्पादन के साथ ऊपर, अच्छा जवाब। यूनिट टेस्ट बनाने और गर्भपात () कॉल को ठोकरते हुए मुझे एक ही समस्या थी, फिर- कंपाइलर stdlib.h में __noreturn__characteristic देखता है, यह जानता है कि कॉल करने के बाद __noreturn__ फ़ंक्शन को कॉल करने के बाद बंद कर सकते हैं, लेकिन जीसीसी और अन्य कंपेलर DO अनुकूलन दबाने के साथ-साथ कोड उत्पन्न करना रोकें कॉल करने के बाद स्टबबेर्ड एबर्ट () के बाद रिटर्न केवल अगले फ़ंक्शन तक पहुंचा, घोषित डेटा आदि। मैंने ऊपर की तरफ दृष्टिकोण की कोशिश की, लेकिन कॉलिंग फ़ंक्शन सिर्फ __wrap_abort () रिटर्न के बाद कोड लापता है।

इस व्यवहार को ओवरराइड करने का एक तरीका मुझे पूर्वप्रक्रमक स्तर पर निरस्तीकरण () घोषणा को पकड़ना है - एक अलग स्रोत फ़ाइल में अपने स्टबबेड एबर्ट () को रखें, और फ़ाइल को abort () के लिए सीएफएलएजी में जोड़ें

-D__noreturn__ = "/ * __noreturn__ * /"

यह stdlib.h में पाया गया घोषणा के प्रभाव को संशोधित करता है जीसीसी-ए के माध्यम से अपने प्रीप्रोसेज़र आउटपुट की जांच करें और यह काम करें सत्यापित करें। आप अपने कंपाइलर के आउटपुट को .o फ़ाइल के objdump के माध्यम से देख सकते हैं।

इस पूरे दृष्टिकोण के स्रोत के लिए कोड उत्पन्न करने का जोड़ा साइड इफेक्ट होगा जो अन्य गर्भपात () कॉलों, बाहर निकलने () कॉलों और कुछ और जो कि __noreturn__ विशेषता के साथ stdlib.h में प्रकट होता है, के बाद होता है, लेकिन हम में से अधिकांश को कोड नहीं है जो किसी निकास () के बाद होता है, और हम में से अधिकांश बस स्टैक को साफ करना चाहते हैं और गर्भपात () कॉलर से वापस आना चाहते हैं।

आप अपने __wrap_abort () कॉल को आह्वान करने के लिए, या, क्योंकि आप __real_abort () को कॉल नहीं करने के लिए लिंकर - लपेट तर्क रख सकते हैं, आप अपने स्टबबेर्ड एबर्ट () को प्राप्त करने के लिए ऊपर के जैसा कुछ कर सकते हैं:

-Dabort = my_stubbed_abort

उम्मीद है की यह मदद करेगा।


यह कला के उत्तर के तहत चर्चा की निरंतरता है, और इसका अर्थ विशुद्ध रूप से एक प्रयोग के रूप में है।

वास्तविक कोड में ऐसा मत करो!

वास्तविक सदन को कॉल करने से पहले, पर्यावरण को बहाल करने के लिए लंबेजम्प का उपयोग करके समस्या को तोड़ दिया जा सकता है।

निम्न प्रोग्राम अपरिभाषित व्यवहार प्रदर्शित नहीं करता है:

#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>

_Noreturn void __real_abort( void ) ;

jmp_buf env ;

_Noreturn void __wrap_abort( void )
{
    printf( "%s\n" , __func__ ) ;
    longjmp( env , 1 ) ;
    __real_abort() ;
}

int main( void )
{

    const int abnormal = setjmp( env ) ;
    if( abnormal )
    {
        printf( "saved!\n" ) ;
    }
    else
    {
        printf( "pre abort\n" ) ;
        abort() ;
        printf( "post abort\n" ) ;
    }

    printf( "EXIT_SUCCESS\n" ) ;
    return EXIT_SUCCESS ;
}

आउटपुट:

pre abort
__wrap_abort
saved!
EXIT_SUCCESS




abort