2012-03-08 43 views
9

Điều này hiện đang chờ xử lý trong danh sách của tôi. Tóm lại - Tôi cần chạy mocked_dummy() thay cho dummy()TRÊN RUN-TIME, mà không sửa đổi factorial(). Tôi không quan tâm đến điểm vào của phần mềm. Tôi có thể thêm bất kỳ số lượng chức năng bổ sung nào (nhưng không thể sửa đổi mã trong phạm vi /*---- do not modify ----*/).Thời gian chạy chế nhạo trong C?

Tại sao tôi cần điều này?
Để thực hiện kiểm tra đơn vị của một số mô-đun C cũ. Tôi biết có rất nhiều công cụ có sẵn xung quanh, nhưng nếu chạy thời gian mocking là có thể tôi có thể thay đổi phương pháp tiếp cận UT của tôi (thêm các thành phần tái sử dụng) làm cho cuộc sống của tôi dễ dàng hơn :).

Nền tảng/Môi trường?
Linux, ARM, gcc.

Phương pháp tiếp cận mà tôi đang thử?

  • Tôi biết GDB sử dụng bẫy/hướng dẫn bất hợp pháp để thêm điểm ngắt (gdb internals).
  • Làm cho mã có thể tự sửa đổi được.
  • Thay thế dummy() phân đoạn mã bằng hướng dẫn bất hợp pháp và trả lại làm hướng dẫn tiếp theo ngay lập tức.
  • Điều khiển chuyển để bẫy người xử lý.
  • Trình xử lý bẫy là chức năng có thể tái sử dụng được đọc từ một ổ cắm miền unix.
  • Địa chỉ của mocked_dummy() chức năng được chuyển (đọc từ tệp bản đồ).
  • Chức năng mô phỏng thực thi.

Có vấn đề xảy ra ở đây. Tôi cũng thấy rằng cách tiếp cận này rất tẻ nhạt và đòi hỏi số lượng mã hóa tốt, một số trong quá trình lắp ráp cũng vậy. Tôi cũng tìm thấy, dưới gcc, mỗi cuộc gọi chức năng có thể là hooked/instrumented, nhưng lại không hữu ích lắm vì chức năng được dự định sẽ được giả lập sẽ được thực hiện.

Có cách tiếp cận nào khác mà tôi có thể sử dụng không?

#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; 
} 

Trả lời

2

Đây là câu hỏi tôi đã cố gắng tự trả lời. Tôi cũng có yêu cầu rằng tôi muốn các phương pháp mocking/công cụ được thực hiện trong cùng một ngôn ngữ như ứng dụng của tôi. Thật không may điều này không thể được thực hiện trong C một cách di động, vì vậy tôi đã dùng đến những gì bạn có thể gọi là một tấm bạt lò xo hoặc đường vòng. Điều này nằm trong phần "Làm cho mã có thể tự sửa đổi được". cách tiếp cận bạn đã đề cập ở trên. Đây là chúng tôi thay đổi byte thực sự của hàm tại thời gian chạy thời gian chạy để chuyển đến chức năng mô phỏng của chúng tôi.

#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; 
} 

Portability lời giải thích ...

mprotect() - Điều này thay đổi quyền truy cập trang bộ nhớ để chúng tôi thực sự có thể ghi vào bộ nhớ chứa mã chức năng. Điều này không phải là rất xách tay, và trong một env WINAPI, bạn có thể cần phải sử dụng VirtualProtect() để thay thế.

Tham số bộ nhớ cho mprotect được căn chỉnh với trang 4k trước đó, điều này cũng có thể thay đổi từ hệ thống thành hệ thống, 4k phù hợp với hạt nhân Linux vanilla.

Phương pháp mà chúng tôi sử dụng để chuyển sang chức năng giả là thực sự đặt các mã opcodes của riêng mình, đây có lẽ là vấn đề lớn nhất với tính di động vì mã tôi đã sử dụng sẽ chỉ hoạt động trên một chút x86 cuối cùng). Vì vậy, điều này sẽ cần phải được cập nhật cho mỗi vòm bạn có kế hoạch để chạy trên (có thể được bán dễ dàng để đối phó trong các macro CPP.)

Bản thân hàm phải có ít nhất năm byte. Trường hợp này thường xảy ra vì mỗi hàm thường là có ít nhất 5 byte trong phần mở đầu và phần kết của nó.

cải tiến tiềm năng ...

Các set_mock_run() gọi có thể dễ dàng được thiết lập để chấp nhận các thông số để tái sử dụng. Ngoài ra, bạn có thể lưu năm byte ghi đè từ hàm ban đầu để khôi phục sau này trong mã nếu bạn muốn.

Tôi không thể kiểm tra, nhưng tôi đã đọc điều đó trong ARM ... bạn sẽ làm tương tự nhưng bạn có thể chuyển đến địa chỉ (không phải là bù đắp) bằng mã opcode ... cho điều kiện vô điều kiện bạn sẽ có byte đầu tiên là 0xEA và 3 byte tiếp theo là địa chỉ.

Chenz

+0

Mã trên sẽ biên dịch như nó được? –

+0

Mã cập nhật để mã được xây dựng. –

+0

Nghi ngờ của tôi là nhiều hơn đối với tuyên bố này '#define dummy (m.dummy)' Làm thế nào bạn sẽ ngăn chặn preprocessor từ NOT thay thế các chức năng giả? –

5

test-dept là một khung kiểm tra đơn vị C tương đối gần đây cho phép bạn thực hiện chức năng chạy theo thời gian. Tôi thấy nó rất dễ sử dụng - đây là một ví dụ từ tài liệu của họ:

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

Mặc dù phần tải xuống hơi lạc hậu, tác giả đã khắc phục vấn đề tôi đã rất nhanh chóng. Bạn có thể nhận được phiên bản mới nhất (mà tôi đã sử dụng mà không vấn đề) với:

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

phiên bản đã có cập nhật trong Tháng Mười 2011.

Tuy nhiên cuối cùng, kể từ khi stubbing là achieved using assembler, nó có thể cần một số nỗ lực để có được nó để hỗ trợ ARM.

+0

Sử dụng phương pháp này, bạn vẫn viết bài kiểm tra trong C thuần túy, nhưng khung sử dụng lắp ráp. Miễn là bạn đang ở trên một nền tảng được hỗ trợ, bạn không thực sự cần phải hiểu (hoặc thậm chí nhìn thấy) hội đồng. Nhưng có, nó có thể không phù hợp với "tinh khiết C" từ đầu đến cuối :) –

3

Cách tiếp cận mà tôi đã sử dụng trong quá khứ đã hoạt động tốt như sau.

Đối với mỗi mô-đun C, xuất bản 'giao diện' mà các mô-đun khác có thể sử dụng. Các giao diện này là các cấu trúc chứa các con trỏ hàm.

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

Trong khi khởi tạo, mỗi mô-đun sẽ khởi tạo các con trỏ chức năng này với các chức năng triển khai.

Khi bạn viết các kiểm tra mô-đun, bạn có thể tự động thay đổi các con trỏ hàm này thành triển khai mô hình của nó và sau khi thử nghiệm, khôi phục triển khai ban đầu.

Ví dụ:

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

Bạn có thể thay thế tất cả các chức năng bằng cách sử dụng LD_PRELOAD. Bạn phải tạo một thư viện được chia sẻ, được tải bởi LD_PRELOAD. Đây là một chức năng tiêu chuẩn được sử dụng để biến các chương trình mà không hỗ trợ SOCKS thành SOCKS aware programs. Here là một hướng dẫn giải thích nó.

Các vấn đề liên quan