लिनक्स में विभाजन दोष को कैसे पकड़ें?


84

मुझे थर्ड पार्टी लाइब्रेरी क्लीनअप ऑपरेशंस में सेगमेंटेशन फॉल्ट को पकड़ने की जरूरत है। मेरे कार्यक्रम से बाहर निकलने से ठीक पहले कभी-कभी ऐसा होता है, और मैं इसके वास्तविक कारण को ठीक नहीं कर सकता। विंडोज प्रोग्रामिंग में मैं __try - __catch के साथ ऐसा कर सकता था। क्या ऐसा करने के लिए क्रॉस-प्लेटफ़ॉर्म या प्लेटफ़ॉर्म-विशिष्ट तरीका है? मुझे लिनक्स में इसकी आवश्यकता है, gcc।


विभाजन दोष हमेशा एक बग के कारण होता है जिसे पकड़ना वास्तव में मुश्किल हो सकता है। मैं बस एक खोज करता हूं जो यादृच्छिक रूप से प्रकट होता है। प्रत्येक फ़ाइल में 500 मिलियन डेटा बिंदु हैं। मोटे तौर पर हर 10-15 फाइलों के बारे में, यह विभाजन दोष दिखाई देता है। मैं मल्टी-थ्रेडिंग, लॉक-फ्री कतार इत्यादि का उपयोग कर रहा था, काफी जटिल नौकरी प्रबंधन। अंत में यह एक ऐसी वस्तु है जिसे मैंने बनाया है, std :: स्थानांतरित () एक और डेटा संरचना में। स्थानीय रूप से मैं इस वस्तु का उपयोग इस कदम के बाद कर रहा था। किसी कारण के लिए, C ++ इसके साथ ठीक है। लेकिन सीगफॉल्ट कुछ बिंदु पर निश्चित रूप से दिखाई देगा।
केमिन झोउ

जवाबों:


80

लिनक्स पर हमारे पास अपवाद के रूप में ये भी हो सकते हैं।

आम तौर पर, जब आपका प्रोग्राम एक विभाजन दोष करता है, तो उसे एक SIGSEGVसंकेत भेजा जाता है । आप इस संकेत के लिए अपना स्वयं का हैंडलर सेट कर सकते हैं और परिणामों को कम कर सकते हैं। बेशक आपको वास्तव में सुनिश्चित होना चाहिए कि आप स्थिति से उबर सकते हैं । आपके मामले में, मुझे लगता है, आपको इसके बजाय अपने कोड को डीबग करना चाहिए।

विषय पर वापस जाएं। मैंने हाल ही में एक पुस्तकालय ( लघु पुस्तिका ) का सामना किया जो ऐसे संकेतों को अपवादों में बदल देता है, इसलिए आप इस तरह कोड लिख सकते हैं:

try
{
    *(int*) 0 = 0;
}
catch (std::exception& e)
{
    std::cerr << "Exception caught : " << e.what() << std::endl;
}

हालांकि यह जाँच नहीं की। मेरे x86-64 Gentoo बॉक्स पर काम करता है। इसमें एक मंच-विशिष्ट बैकेंड है (जीसीसी के जावा कार्यान्वयन से उधार लिया गया है), इसलिए यह कई प्लेटफार्मों पर काम कर सकता है। यह सिर्फ x86 और x86-64 बॉक्स से बाहर का समर्थन करता है, लेकिन आप libjava से बैकएंड प्राप्त कर सकते हैं, जो gcc स्रोतों में रहता है।


16
+1 यह सुनिश्चित करने के लिए कि आप सिग सेगफॉल्ट को पकड़ने से पहले ठीक हो सकते हैं
हेनरिक मुथ

16
सिग्नल हैंडलर से फेंकना बहुत खतरनाक काम है। अधिकांश कंपाइलर मानते हैं कि केवल कॉल अपवाद पैदा कर सकते हैं, और तदनुसार जानकारी को सेट कर सकते हैं। जावा और सी # जैसे सॉफ़्टवेयर अपवादों में हार्डवेयर अपवादों को बदलने वाली भाषाएं, जानते हैं कि कुछ भी फेंक सकती हैं; C ++ के मामले में ऐसा नहीं है। जीसीसी के साथ, आपको कम से कम -fnon-call-exceptionsयह सुनिश्चित करने की आवश्यकता है कि यह काम करता है - और इसके लिए एक प्रदर्शन लागत है। एक खतरा यह भी है कि आप बिना अपवाद समर्थन (जैसे सी फ़ंक्शन) और लीक / क्रैश के बाद एक फ़ंक्शन से फेंक देंगे।
जंक

1
मैं zneak से सहमत हूं। सिग्नल हैंडलर से न फेंके।
एम.एम.

पुस्तकालय अब github.com/Plaristote/segvcatch में है , लेकिन मैं मैनुअल नहीं ढूँढ सका या इसे संकलित नहीं कर सका। ./build_gcc_linux_releaseकई त्रुटियां देता है।
alfC

वाह! अब मुझे पता है कि मैं दुनिया में केवल Gentoo उपयोगकर्ता नहीं हूँ!
एसएस ऐनी

46

सी में कैसे करें, इसका एक उदाहरण यहां दिया गया है।

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void segfault_sigaction(int signal, siginfo_t *si, void *arg)
{
    printf("Caught segfault at address %p\n", si->si_addr);
    exit(0);
}

int main(void)
{
    int *foo = NULL;
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = segfault_sigaction;
    sa.sa_flags   = SA_SIGINFO;

    sigaction(SIGSEGV, &sa, NULL);

    /* Cause a seg fault */
    *foo = 1;

    return 0;
}

9
sizeof (sigaction) ==> sizeof (स्ट्रक्चर सिगनेशन), या फिर आपको चीज़ को संकलित करने में ISO C ++ त्रुटि मिलती है।
डेव डोपसन

7
सिग्नल हैंडलर में IO करना आपदा के लिए एक नुस्खा है।
टिम सेग्विन

6
@TimSeguine: यह सच नहीं है। आपको केवल यह सुनिश्चित करने की आवश्यकता है कि आप जानते हैं कि आप क्या कर रहे हैं। signal(7)सभी async-signal-safe फ़ंक्शन को सूचीबद्ध करता है जो अपेक्षाकृत कम देखभाल के साथ उपयोग किए जा सकते हैं। ऊपर दिए गए उदाहरण में यह पूरी तरह से सुरक्षित भी है क्योंकि कार्यक्रम में कुछ भी नहीं छू रहा है stdoutलेकिन printfहैंडलर में कॉल है।
स्टीफनक्ट

3
@stefanct यह एक खिलौना उदाहरण है। वस्तुतः कोई भी गैर-खिलौना कार्यक्रम किसी बिंदु पर स्टडआउट पर लॉक को धारण करने वाला है। इस सिग्नल हैंडलर के साथ, सबसे खराब जो शायद हो सकता है, सेगफॉल्ट पर गतिरोध है, लेकिन यह काफी खराब हो सकता है यदि आपके पास वर्तमान में आपके उपयोग के मामले में दुष्ट प्रक्रियाओं को मारने के लिए कोई तंत्र नहीं है।
टिम सेग्विन

3
2.4.3 सिग्नल क्रियाओं के अनुसार , सिग्नल हैंडलर के भीतर से प्रिंटफ को कॉल करना, जिसे एक अवैध अप्रत्यक्ष परिणाम के रूप में कहा जाता है, चाहे प्रोग्राम मल्टीथ्रेडेड हो या न हो, केवल सादे अपरिभाषित व्यवहार अवधि है।
जूलियन विलेमुरे-फ्रेचेते

9

पोर्टेबिलिटी के लिए, शायद का उपयोग करना चाहिए std::signal मानक सी ++ लाइब्रेरी से , लेकिन सिग्नल हैंडलर क्या कर सकता है, इस पर बहुत प्रतिबंध है। दुर्भाग्य से, अपरिभाषित व्यवहार की शुरुआत किए बिना C ++ प्रोग्राम के भीतर से SIGSEGV को पकड़ना संभव नहीं है, क्योंकि विनिर्देश:

  1. यह मानक पुस्तकालय कार्यों के एक बहुत ही संकीर्ण सबसेट के अलावा हैंडलर के भीतर से किसी भी पुस्तकालय समारोह को कॉल करने के लिए अपरिभाषित व्यवहार है abort, औरexit कुछ परमाणु काम करता है, वर्तमान संकेत हैंडलर, को पुनर्स्थापित memcpy, memmoveटाइप करें लक्षण, `std :: चाल, std::forward है, और कुछ और )।
  2. यदि हैंडलर a का उपयोग करता है तो यह अपरिभाषित व्यवहार है throw अभिव्यक्ति का है।
  3. यह अनिर्धारित व्यवहार है यदि हैंडलर SIGFPE, SIGILL, SIGSEGV को हैंडल करते समय वापस आता है

यह साबित करता है कि सख्ती से मानक और पोर्टेबल सी ++ का उपयोग करके प्रोग्राम के भीतर से एसआईजीएसईजीवी को पकड़ना असंभव है । SIGSEGV अभी भी ऑपरेटिंग सिस्टम द्वारा पकड़ा जाता है और आम तौर पर मूल प्रक्रिया को सूचित किया जाता है जब a प्रतीक्षा पारिवारिक फ़ंक्शन कहा जाता है।

आप संभवतः POSIX सिग्नल का उपयोग करते हुए एक ही तरह की परेशानी में भागेंगे क्योंकि 2.4.3 सिग्नल क्रियाओं में एक क्लॉज है जो कहता है :

के बाद यह एक SIGBUS, SIGFPE, SIGILL, या SIGSEGV संकेत है कि द्वारा उत्पन्न नहीं किया गया था के लिए एक संकेत को पकड़ने समारोह से सामान्य रूप से देता है एक प्रक्रिया के व्यवहार अपरिभाषित है kill(), sigqueue()या raise()

longjumpएस के बारे में एक शब्द । यह मानते हुए कि हम POSIX संकेतों का उपयोग कर रहे हैं, longjumpस्टैक अनइंडिंग का अनुकरण करने से मदद नहीं मिलेगी:

हालाँकि, longjmp()यह एक async-signal-safe function है, अगर यह एक सिग्नल हैंडलर से मंगवाया जाता है, जो एक गैर-async-signal-safe फ़ंक्शन या समतुल्य को बाधित करता है (जैसे exit()कि प्रारंभिक कॉल से वापसी के बाद किया गया प्रोसेसिंग के बराबर main()) किसी गैर-एसिंक्स-सिग्नल-सुरक्षित फ़ंक्शन या समकक्ष के लिए किसी भी बाद की कॉल का व्यवहार अपरिभाषित है।

इसका अर्थ है कि कॉल द्वारा लॉन्गजंप पर जारी की गई निरंतरता आमतौर पर उपयोगी लाइब्रेरी फ़ंक्शन जैसे कि printf, mallocया exitअपरिभाषित व्यवहार को प्रेरित किए बिना मुख्य से वापस आने को मज़बूती से नहीं कह सकती है। जैसे, निरंतरता केवल एक प्रतिबंधित संचालन कर सकती है और केवल कुछ असामान्य समाप्ति तंत्र के माध्यम से बाहर निकल सकती है।

चीजों को कम करने के लिए, एक SIGSEGV को पकड़ना और एक पोर्टेबल में कार्यक्रम के निष्पादन को फिर से शुरू करना संभवत: यूबी की शुरुआत के बिना संभव है। यहां तक ​​कि अगर आप एक Windows प्लेटफ़ॉर्म पर काम कर रहे हैं जिसके लिए आपके पास संरचित अपवाद हैंडलिंग तक पहुंच है, तो यह उल्लेखनीय है कि MSDN हार्डवेयर अपवादों को संभालने का प्रयास न करने का सुझाव देता है: हार्डवेयर अपवाद


SIGSEGV शायद ही एक हार्डवेयर अपवाद है, हालांकि। कोई भी हमेशा एक पैरेंट-चाइल्ड आर्किटेक्चर का उपयोग कर सकता है, जहां माता-पिता एक बच्चे के मामले का पता लगाने में सक्षम होते हैं जो कर्नेल द्वारा मारे गए और फिर से शुरू करने के लिए प्रासंगिक प्रोग्राम स्थिति साझा करने के लिए आईपीसी का उपयोग करें। मेरा मानना ​​है कि आधुनिक ब्राउज़रों को इस तरह से देखा जा सकता है, क्योंकि वे आईपीसी तंत्र का उपयोग प्रति ब्राउज़र टैब के साथ उस एक प्रक्रिया के साथ संवाद करने के लिए करते हैं। जाहिर है प्रक्रियाओं के बीच सुरक्षा सीमा ब्राउज़र परिदृश्य में एक बोनस है।
0xC0000022L

8

सी ++ समाधान यहां पाया गया ( http://www.cplusplus.com/forum/unices/16430/ )

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
}
int main()
{
    struct sigaction act;
    act.sa_handler = ouch;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);
    while(1) {
        printf("Hello World!\n");
        sleep(1);
    }
}

7
मुझे पता है कि यह केवल एक उदाहरण है जिसे आपने नहीं लिखा था, लेकिन सिग्नल हैंडलर में IO करना आपदा के लिए एक नुस्खा है।
टिम सेगुइन

3
@TimSeguine: ऐसे सामान को दोहराना जो बहुत ही भ्रामक है, यह एक अच्छा विचार नहीं है (cf. stackoverflow.com/questions/2350489/… )
stefanct

3
@stefanct सिग्नल हैंडलर में सुरक्षित रूप से प्रिंटफ का उपयोग करने के लिए आवश्यक सावधानियां तुच्छ नहीं हैं। उसके बारे में कुछ भी भ्रामक नहीं है। यह एक खिलौना उदाहरण है। और इस खिलौना उदाहरण में भी अगर आप सही समय का गतिरोध संभव है। डेडलॉक खतरनाक हैं सटीक रूप से वे दुर्लभ हैं। अगर आपको लगता है कि यह सलाह भ्रामक थी, तो मेरे कोड से दूर रहें, क्योंकि मैं आपको इसके एक मील के भीतर भरोसा नहीं करता।
टिम सेग्विन

फिर, आप यहाँ सामान्य रूप से I / O के बारे में बात कर रहे थे। इस वास्तविक उदाहरण के साथ समस्या को इंगित करने के बजाय, जो वास्तव में एक बुरा है।
स्टीफनैक्ट

1
@stefanct यदि आप बयान के संदर्भ को निपिक और अनदेखा करना चाहते हैं, तो यह आपकी समस्या है। किसने कहा कि मैं सामान्य रूप से I / O के बारे में बात कर रहा था? आप। मुझे बस लोगों के साथ एक बड़ी समस्या है कि वे मुश्किल समस्याओं के लिए खिलौना जवाब पोस्ट कर रहे हैं। यहां तक ​​कि मामले में आप async सुरक्षित फ़ंक्शंस का उपयोग करते हैं, इसके बारे में सोचने के लिए अभी भी बहुत कुछ है और यह उत्तर ऐसा लगता है जैसे यह तुच्छ है।
टिम Seguine

5

कभी-कभी हम SIGSEGVयह पता लगाना चाहते हैं कि क्या कोई पॉइंटर वैध है, अर्थात यदि वह वैध मेमोरी एड्रेस को संदर्भित करता है। (या यह भी जांच लें कि कुछ मनमाना मान सूचक हो सकता है।)

एक विकल्प यह है कि इसे isValidPtr()Android पर काम किया जाए:

int isValidPtr(const void*p, int len) {
    if (!p) {
    return 0;
    }
    int ret = 1;
    int nullfd = open("/dev/random", O_WRONLY);
    if (write(nullfd, p, len) < 0) {
    ret = 0;
    /* Not OK */
    }
    close(nullfd);
    return ret;
}
int isValidOrNullPtr(const void*p, int len) {
    return !p||isValidPtr(p, len);
}

एक अन्य विकल्प मेमोरी प्रोटेक्शन विशेषताओं को पढ़ना है, जो थोड़ा और मुश्किल है (एंड्रॉइड पर काम किया गया है):

re_mprot.c:

#include <errno.h>
#include <malloc.h>
//#define PAGE_SIZE 4096
#include "dlog.h"
#include "stdlib.h"
#include "re_mprot.h"

struct buffer {
    int pos;
    int size;
    char* mem;
};

char* _buf_reset(struct buffer*b) {
    b->mem[b->pos] = 0;
    b->pos = 0;
    return b->mem;
}

struct buffer* _new_buffer(int length) {
    struct buffer* res = malloc(sizeof(struct buffer)+length+4);
    res->pos = 0;
    res->size = length;
    res->mem = (void*)(res+1);
    return res;
}

int _buf_putchar(struct buffer*b, int c) {
    b->mem[b->pos++] = c;
    return b->pos >= b->size;
}

void show_mappings(void)
{
    DLOG("-----------------------------------------------\n");
    int a;
    FILE *f = fopen("/proc/self/maps", "r");
    struct buffer* b = _new_buffer(1024);
    while ((a = fgetc(f)) >= 0) {
    if (_buf_putchar(b,a) || a == '\n') {
        DLOG("/proc/self/maps: %s",_buf_reset(b));
    }
    }
    if (b->pos) {
    DLOG("/proc/self/maps: %s",_buf_reset(b));
    }
    free(b);
    fclose(f);
    DLOG("-----------------------------------------------\n");
}

unsigned int read_mprotection(void* addr) {
    int a;
    unsigned int res = MPROT_0;
    FILE *f = fopen("/proc/self/maps", "r");
    struct buffer* b = _new_buffer(1024);
    while ((a = fgetc(f)) >= 0) {
    if (_buf_putchar(b,a) || a == '\n') {
        char*end0 = (void*)0;
        unsigned long addr0 = strtoul(b->mem, &end0, 0x10);
        char*end1 = (void*)0;
        unsigned long addr1 = strtoul(end0+1, &end1, 0x10);
        if ((void*)addr0 < addr && addr < (void*)addr1) {
            res |= (end1+1)[0] == 'r' ? MPROT_R : 0;
            res |= (end1+1)[1] == 'w' ? MPROT_W : 0;
            res |= (end1+1)[2] == 'x' ? MPROT_X : 0;
            res |= (end1+1)[3] == 'p' ? MPROT_P
                 : (end1+1)[3] == 's' ? MPROT_S : 0;
            break;
        }
        _buf_reset(b);
    }
    }
    free(b);
    fclose(f);
    return res;
}

int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask) {
    unsigned prot1 = read_mprotection(addr);
    return (prot1 & prot_mask) == prot;
}

char* _mprot_tostring_(char*buf, unsigned int prot) {
    buf[0] = prot & MPROT_R ? 'r' : '-';
    buf[1] = prot & MPROT_W ? 'w' : '-';
    buf[2] = prot & MPROT_X ? 'x' : '-';
    buf[3] = prot & MPROT_S ? 's' : prot & MPROT_P ? 'p' :  '-';
    buf[4] = 0;
    return buf;
}

re_mprot.h:

#include <alloca.h>
#include "re_bits.h"
#include <sys/mman.h>

void show_mappings(void);

enum {
    MPROT_0 = 0, // not found at all
    MPROT_R = PROT_READ,                                 // readable
    MPROT_W = PROT_WRITE,                                // writable
    MPROT_X = PROT_EXEC,                                 // executable
    MPROT_S = FIRST_UNUSED_BIT(MPROT_R|MPROT_W|MPROT_X), // shared
    MPROT_P = MPROT_S<<1,                                // private
};

// returns a non-zero value if the address is mapped (because either MPROT_P or MPROT_S will be set for valid addresses)
unsigned int read_mprotection(void* addr);

// check memory protection against the mask
// returns true if all bits corresponding to non-zero bits in the mask
// are the same in prot and read_mprotection(addr)
int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask);

// convert the protection mask into a string. Uses alloca(), no need to free() the memory!
#define mprot_tostring(x) ( _mprot_tostring_( (char*)alloca(8) , (x) ) )
char* _mprot_tostring_(char*buf, unsigned int prot);

पुनश्च DLOG()है printf()एंड्रॉयड लॉग करने के लिए। यहाँFIRST_UNUSED_BIT() परिभाषित किया गया है

पीपीएस यह लूप में एलोका () को कॉल करने के लिए एक अच्छा विचार नहीं हो सकता है - फ़ंक्शन वापस आने तक मेमोरी को मुक्त नहीं किया जा सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.