2012-01-25 69 views
6

Giả sử tôi có một chức năng:C: Ghi đè lên một chức năng byte bởi byte

int f1(int x){ 
// some more or less complicated operations on x 
return x; 
} 

Và rằng tôi có chức năng khác

int f2(int x){ 
// we simply return x 
return x; 
} 

Tôi muốn để có thể làm điều gì đó như sau :

char* _f1 = (char*)f1; 
char* _f2 = (char*)f2; 
int i; 
for (i=0; i<FUN_LENGTH; ++i){ 
f1[i] = f2[i]; 
} 

Tức là Tôi muốn giải thích f1f2 làm mảng byte thô và "ghi đè f1 byte theo byte" và do đó, thay thế bằng f2.

Tôi biết rằng mã thường có thể gọi được bảo vệ chống ghi, tuy nhiên, trong trường hợp cụ thể của tôi, bạn chỉ cần ghi đè vị trí bộ nhớ nơi đặt f1. Tức là, tôi có thể sao chép các byte lên trên f1, nhưng sau đó, nếu tôi gọi f1, toàn bộ sự cố sẽ bị treo.

Vì vậy, phương pháp tiếp cận của tôi có thể về nguyên tắc không? Hoặc có một số máy/thực hiện/bất kỳ vấn đề phụ thuộc nào mà tôi phải cân nhắc không?

+0

Tôi tin rằng "mã có thể gọi được bảo vệ chống ghi" là câu trả lời cho lý do tại sao nó không thành công. Tôi nghi ngờ tôi là người đầu tiên nói điều này, nhưng tự sửa đổi mã thường là một ý tưởng khủng khiếp hoặc một triệu chứng của một lỗi. – DwB

+0

@DwB Như tôi đã đề cập trong câu hỏi của mình, tôi phát hiện ra rằng tôi * có thể * ghi vào phần mà các chức năng được lưu trữ. Chỉ cần * gọi * biến thể ghi đè dẫn đến sự cố. – phimuemue

+0

Ngoài ra, hãy xem xét rằng f1 có lẽ dài hơn f2 ... tức là được tạo thành từ nhiều byte –

Trả lời

8

Sẽ dễ dàng hơn để thay thế một vài byte đầu tiên của f1 bằng máy jump hướng dẫn về đầu f2. Bằng cách đó, bạn sẽ không phải đối phó với bất kỳ vấn đề di chuyển mã có thể có.

Ngoài ra, thông tin về số byte chứa hàm (FUN_LENGTH trong câu hỏi của bạn) thường không có sẵn khi chạy. Sử dụng jump cũng sẽ tránh được sự cố đó.

Đối với x86, mã lệnh nhảy tương đối mà bạn cần là E9 (theo here). Đây là bước nhảy tương đối 32 bit, có nghĩa là bạn cần tính toán độ lệch tương đối giữa f2f1. Mã này có thể làm điều đó:

int offset = (int)f2 - ((int)f1 + 5); // 5 bytes for size of instruction 
char *pf1 = (char *)f1; 
pf1[0] = 0xe9; 
pf1[1] = offset & 0xff; 
pf1[2] = (offset >> 8) & 0xff; 
pf1[3] = (offset >> 16) & 0xff; 
pf1[4] = (offset >> 24) & 0xff; 

Các bù đắp được lấy từ cuối của lệnh JMP, vì vậy đó là lý do tại sao có 5 thêm vào địa chỉ của f1 trong việc tính toán bù đắp.

Đó là một ý tưởng hay để duyệt qua kết quả với trình gỡ lỗi mức lắp ráp để đảm bảo bạn đang ping đúng các byte. Tất nhiên, đây là tất cả các tiêu chuẩn không tuân thủ vì vậy nếu nó phá vỡ bạn nhận được để giữ cho cả hai phần.

+0

Điều đó chắc chắn sẽ là một lựa chọn. Bạn có biết cách thực hiện điều này trong chương trình C không? – phimuemue

+0

Chắc chắn, lấy địa chỉ của 'f1' và đưa nó vào một' char * '. Sau đó, ghi đè lên một vài byte đầu tiên với một lệnh máy thích hợp cho kiến ​​trúc của bạn gây ra một bước nhảy tới 'f2'. Bạn có thể sử dụng một địa chỉ tuyệt đối hoặc bạn có thể cần tính toán độ lệch tương đối và sử dụng nó. Không thể nói chi tiết hơn mà không biết bạn đang sử dụng kiến ​​trúc nào. –

+1

Loại CPU nào chúng ta đang nói đến? –

0

Hầu hết các kiến ​​trúc bộ nhớ sẽ ngăn bạn viết mã chức năng. Nó sẽ sụp đổ .... Nhưng một số thiết bị nhúng, bạn có thể làm điều này, nhưng nó nguy hiểm trừ khi bạn biết có đủ không gian, cuộc gọi sẽ ổn, ngăn xếp sẽ ổn, v.v. ..

Rất có thể có cách nào tốt hơn để giải quyết vấn đề.

1

Cách tiếp cận của bạn là hành vi không xác định cho tiêu chuẩn C.

Và trên nhiều hệ điều hành (ví dụ:Linux), ví dụ của bạn sẽ bị lỗi: mã chức năng nằm bên trong phân đoạn chỉ đọc .text (và phần) của tệp thực thi ELF và phân đoạn đó (sắp xếp) mmap-chỉ đọc bởi execve (hoặc bằng dlopen hoặc bằng liên kết động), vì vậy bạn không thể viết bên trong nó.

+1

Có, và không chỉ vì nó cố sửa đổi mã. Chuyển đổi một con trỏ hàm thành 'char *' có hành vi không xác định. –

+0

Tuy nhiên, con trỏ hàm tới 'void *' được cho phép trong tiêu chuẩn Posix mới nhất (nhưng không phải trong tiêu chuẩn C, và một số kiến ​​trúc lạ có kích thước khác nhau cho con trỏ tới hàm và con trỏ tới dữ liệu). –

1

Thay vì cố gắng ghi đè lên các chức năng (mà bạn đã tìm thấy là mong manh lúc tốt nhất), tôi muốn xem xét sử dụng một con trỏ tới một hàm:

int complex_implementation(int x) { 
    // do complex stuff with x 
    return x; 
} 

int simple_implementation(int x) { 
    return x; 
} 

int (*f1)(int) = complex_implementation; 

Bạn muốn sử dụng cái gì này như sau:

for (int i=0; i<limit; i++) { 
    a = f1(a); 
    if (whatever_condition) 
     f1 = simple_implementation; 
} 

... và sau khi chuyển nhượng, gọi f1 sẽ chỉ trả về giá trị đầu vào. Gọi cho một chức năng thông qua một con trỏ không áp đặt một số chi phí, nhưng (nhờ đó là phổ biến trong các ngôn ngữ OO) hầu hết các trình biên dịch và CPU làm một công việc khá tốt của việc giảm thiểu chi phí đầu vào đó.

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