2012-03-08 14 views
9

यह मेरी सूची में अब तक लंबे समय से लंबित है। संक्षेप में - मुझे संशोधित किए बिना dummy()ऑन रन-टाइम पर mocked_dummy() चलाने की आवश्यकता है। मुझे सॉफ्टवेयर के प्रवेश बिंदु पर परवाह नहीं है। मैं किसी भी अतिरिक्त फ़ंक्शन को जोड़ सकता हूं (लेकिन /*---- do not modify ----*/ के भीतर कोड संशोधित नहीं कर सकता)।सी में रन-टाइम मॉकिंग?

मुझे इसकी आवश्यकता क्यों है?
कुछ विरासत सी मॉड्यूल के यूनिट परीक्षण करने के लिए। मुझे पता है कि बहुत सारे टूल्स उपलब्ध हैं, लेकिन यदि रन-टाइम मॉकिंग संभव है तो मैं अपना यूटी दृष्टिकोण बदल सकता हूं (पुन: प्रयोज्य घटकों को जोड़ सकता हूं) जिससे मेरा जीवन आसान हो जाता है :)।

प्लेटफार्म/पर्यावरण?
लिनक्स, एआरएम, जीसीसी।

दृष्टिकोण जिसके साथ मैं कोशिश कर रहा हूं?

  • मुझे पता है कि जीडीबी ब्रेकपॉइंट्स जोड़ने के लिए जाल/अवैध निर्देशों का उपयोग करता है (gdb internals)।
  • कोड स्वयं संशोधित करें।
  • अवैध निर्देश के साथ dummy() कोड सेगमेंट बदलें, और तत्काल अगले निर्देश के रूप में लौटाएं।
  • जाल हैंडलर को नियंत्रण स्थानान्तरण।
  • ट्रैप हैंडलर एक पुन: प्रयोज्य फ़ंक्शन है जो यूनिक्स डोमेन सॉकेट से पढ़ता है।
  • mocked_dummy() फ़ंक्शन का पता पारित किया गया है (मानचित्र फ़ाइल से पढ़ें)।
  • नकली फ़ंक्शन निष्पादित करता है।

यहां से आगे की समस्याएं हैं। मैंने यह भी पाया कि दृष्टिकोण कठिन है और कोडिंग की अच्छी मात्रा की आवश्यकता है, कुछ असेंबली में भी।

मुझे यह भी पता चला कि जीसीसी के तहत प्रत्येक फंक्शन कॉल hooked/instrumented हो सकता है, लेकिन फिर से बहुत उपयोगी नहीं है क्योंकि फ़ंक्शन का मजाक उड़ाया जाने वाला है, फिर भी इसे निष्पादित किया जाएगा।

क्या कोई अन्य दृष्टिकोण है जिसका मैं उपयोग कर सकता हूं?

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

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

/*---- do not modify ----*/ 
void dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

int factorial(int num) 
{ 
    int      fact = 1; 
    printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 
/*---- do not modify ----*/ 

int main(int argc, char * argv[]) 
{ 
    int (*fp)(int) = atoi(argv[1]); 
    printf("fp = %x\n",fp); 
    printf("factorial of 5 is = %d\n",fp(5)); 
    printf("factorial of 5 is = %d\n",factorial(5)); 
    return 1; 
} 

उत्तर

2

यह एक सवाल है जिसे मैं स्वयं जवाब देने का प्रयास कर रहा हूं। मुझे यह भी आवश्यकता है कि मैं अपने आवेदन के समान भाषा में मॉकिंग विधि/उपकरण करना चाहता हूं। दुर्भाग्यवश यह सी में पोर्टेबल तरीके से नहीं किया जा सकता है, इसलिए मैंने इसका उपयोग किया है कि आप ट्रैम्पोलिन या डिटोर कह सकते हैं। यह "कोड स्वयं संशोधित करें" के अंतर्गत आता है। ऊपर वर्णित दृष्टिकोण। यह हम अपने मॉक फ़ंक्शन पर जाने के लिए रनटाइम पर फ़ंक्शन के वास्तव में बाइट्स को बदलते हैं।

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

// Additional headers 
#include <stdint.h> // for uint32_t 
#include <sys/mman.h> // for mprotect 
#include <errno.h> // for errno 

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

/*---- do not modify ----*/ 
void dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

int factorial(int num) 
{ 
    int      fact = 1; 
    printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 
/*---- do not modify ----*/ 

typedef void (*dummy_fun)(void); 

void set_run_mock() 
{ 
    dummy_fun run_ptr, mock_ptr; 
    uint32_t off; 
    unsigned char * ptr, * pg; 

    run_ptr = dummy; 
    mock_ptr = mocked_dummy; 

    if (run_ptr > mock_ptr) { 
     off = run_ptr - mock_ptr; 
     off = -off - 5; 
    } 
    else { 
     off = mock_ptr - run_ptr - 5; 
    } 

    ptr = (unsigned char *)run_ptr; 

    pg = (unsigned char *)(ptr - ((size_t)ptr % 4096)); 
    if (mprotect(pg, 5, PROT_READ | PROT_WRITE | PROT_EXEC)) { 
     perror("Couldn't mprotect"); 
     exit(errno); 
    } 

    ptr[0] = 0xE9; //x86 JMP rel32 
    ptr[1] = off & 0x000000FF; 
    ptr[2] = (off & 0x0000FF00) >> 8; 
    ptr[3] = (off & 0x00FF0000) >> 16; 
    ptr[4] = (off & 0xFF000000) >> 24; 
} 

int main(int argc, char * argv[]) 
{ 
    // Run for realz 
    factorial(5); 

    // Set jmp 
    set_run_mock(); 

    // Run the mock dummy 
    factorial(5); 

    return 0; 
} 

पोर्टेबिलिटी स्पष्टीकरण ...

mprotect() - यह स्मृति पेज पहुँच अनुमतियाँ बदल जाता है ताकि हम वास्तव में याद है कि समारोह कोड धारण करने के लिए लिख सकते हैं। यह बहुत पोर्टेबल नहीं है, और एक WINAPI env में, आपको इसके बजाय VirtualProtect() का उपयोग करने की आवश्यकता हो सकती है।

mprotect के लिए स्मृति पैरामीटर पिछले 4k पृष्ठ से गठबंधन है, यह सिस्टम से सिस्टम में भी बदल सकता है, 4k वेनिला लिनक्स कर्नेल के लिए उपयुक्त है।

जिस विधि का उपयोग हम मॉक फ़ंक्शन में जेएमपी करने के लिए करते हैं, वह वास्तव में अपने स्वयं के ऑपकोड डालना है, यह शायद पोर्टेबिलिटी के साथ सबसे बड़ा मुद्दा है क्योंकि मैंने जो ऑपोड इस्तेमाल किया है वह केवल थोड़ा एंडियन x86 (अधिकांश डेस्कटॉप पर काम करेगा))। इसलिए इसे प्रत्येक आर्क के लिए अपडेट करने की आवश्यकता होगी जिसे आप चलाने के लिए योजना बना सकते हैं (जो सीपीपी मैक्रोज़ में सौदा करने के लिए अर्ध-आसान हो सकता है।)

फ़ंक्शन को कम से कम पांच बाइट होना चाहिए। आम तौर पर यह मामला है क्योंकि प्रत्येक कार्य सामान्यतः में इसके प्रस्तावना और उपन्यास में कम से कम 5 बाइट हैं।

संभावित सुधार ...

set_mock_run() कॉल आसानी से पुनः उपयोग के लिए मानकों को स्वीकार करने के लिए सेटअप हो सकता है। साथ ही, यदि आप चाहें तो कोड में बाद में पुनर्स्थापित करने के लिए आप मूल फ़ंक्शन से पांच ओवरराइट बाइट्स को सहेज सकते हैं।

मैं परीक्षण करने में असमर्थ हूं, लेकिन मैंने इसे एआरएम में पढ़ा है ... आप समान काम करेंगे लेकिन आप शाखा ओपोड के साथ एक पते (ऑफसेट नहीं) पर जा सकते हैं ... जो बिना शर्त के शाखा जिसमें आपके पास पहला बाइट 0xEA होगा और अगले 3 बाइट पते हैं।

चेनज़

+0

क्या उपर्युक्त कोड संकलित होगा? –

+0

अद्यतन कोड इसलिए यह बनाता है। –

+0

मेरा संदेह इस कथन की ओर अधिक था '#define डमी (m.dummy)' आप प्रीप्रोसेसर को डमी फ़ंक्शन को प्रतिस्थापित करने से कैसे रोकेंगे? –

5

test-dept एक अपेक्षाकृत हाल सी इकाई परीक्षण रूपरेखा है कि आप कार्यों के क्रम छोटा करते करने की अनुमति देता है। मैंने पाया यह बहुत आसान उपयोग करने के लिए - यहाँ उनके डॉक्स से एक उदाहरण है:

void test_stringify_cannot_malloc_returns_sane_result() { 
    replace_function(&malloc, &always_failing_malloc); 
    char *h = stringify('h'); 
    assert_string_equals("cannot_stringify", h); 
} 

हालांकि डाउनलोड अनुभाग एक छोटे से पुराना हो चुका है, यह काफी सक्रिय रूप से विकसित लगता है - लेखक एक मुद्दा मैं बहुत तुरंत था तय की। पिछले अक्टूबर में अद्यतन 2011

लेकिन वहाँ था, के बाद से छोटा करते achieved using assembler है, यह आवश्यकता हो सकती है

svn checkout http://test-dept.googlecode.com/svn/trunk/ test-dept-read-only 

संस्करण: आप के साथ नवीनतम संस्करण (जो मैं मुद्दों के बिना उपयोग कर रहे हैं) प्राप्त कर सकते हैं एआरएम का समर्थन करने के लिए इसे प्राप्त करने के कुछ प्रयास।

+0

इस दृष्टिकोण का उपयोग करके, आप अभी भी शुद्ध परीक्षण में अपने परीक्षण लिखते हैं, लेकिन ढांचा असेंबली का उपयोग करता है। जब तक आप एक समर्थित मंच पर हों, आपको असेंबली को समझने की आवश्यकता नहीं है (या यहां तक ​​कि देखें)। लेकिन हाँ, यह शायद अंत से अंत तक "शुद्ध सी" फिट नहीं है :) –

3

एक दृष्टिकोण जिसे मैंने अतीत में उपयोग किया है, वह अच्छी तरह से काम कर रहा है।

प्रत्येक सी मॉड्यूल के लिए, एक 'इंटरफेस' प्रकाशित करें जो अन्य मॉड्यूल का उपयोग कर सकते हैं। ये इंटरफेस ऐसे structs हैं जिनमें फ़ंक्शन पॉइंटर्स होते हैं।

struct Module1 
{ 
    int (*getTemperature)(void); 
    int (*setKp)(int Kp); 
} 

प्रारंभ के दौरान, प्रत्येक मॉड्यूल इन कार्यान्वयन कार्यों के साथ इन फ़ंक्शन पॉइंटर्स को प्रारंभ करता है।

जब आप मॉड्यूल परीक्षण लिखते हैं, तो आप गतिशील रूप से इन फ़ंक्शन पॉइंटर्स को अपने नकली कार्यान्वयन में बदल सकते हैं और परीक्षण के बाद, मूल कार्यान्वयन को पुनर्स्थापित कर सकते हैं।

उदाहरण:

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 
/*---- do not modify ----*/ 
void dummyFn(void) 
{ 
    printf("__%s__()\n",__func__); 
} 
static void (*dummy)(void) = dummyFn; 
int factorial(int num) 
{ 
    int      fact = 1; 
     printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 

/*---- do not modify ----*/ 
int main(int argc, char * argv[]) 
{ 
    void (*oldDummy) = dummy; 

/* with the original dummy function */ 
    printf("factorial of 5 is = %d\n",factorial(5)); 

/* with the mocked dummy */ 
    oldDummy = dummy; /* save the old dummy */ 
    dummy = mocked_dummy; /* put in the mocked dummy */ 
    printf("factorial of 5 is = %d\n",factorial(5)); 
    dummy = oldDummy; /* restore the old dummy */ 
    return 1; 
} 
2

आप LD_PRELOAD के उपयोग के द्वारा हर कार्य बदल सकते हैं। आपको एक साझा लाइब्रेरी बनाना है, जो LD_PRELOAD द्वारा लोड हो जाता है। यह एक मानक फ़ंक्शन है जो सॉक्स के लिए SOCKS aware programs में समर्थन के बिना प्रोग्राम को चालू करने के लिए उपयोग किया जाता है। Here एक ट्यूटोरियल है जो इसे बताता है।

संबंधित मुद्दे