PHP: Fix bug PHP Core

2103

Hôm nay, chúng ta sẽ tìm hiểu về PHP Core. Trước đó, ta sẽ xem xét workflow cho một bug đơn giản trong core.

Mục đích của bài viết này không phải để dạy các bạn cách thêm tính năng mới vào PHP. Nếu bạn muốn tìm hiểu thêm về vấn đề này, bạn có thể đọc thêm bài viết này..

Sửa lỗi

Sửa lỗi trong core (chỉ yêu cầu bạn biết C cơ bản) là một cách hay nếu bạn muốn làm quen hoặc cải thiện kiến thức về môi trường PHP. Trước hết, bạn cần phải làm quen với việc quản lý phiên bản PHP.

PHP Version Management Lifecycle

Phiên bản phụ PHP (PHP minor version) thường đi theo chu kỳ năm, mỗi phiên bản phụ sẽ được hỗ trợ trong 3 năm. Hai năm đầu là “hỗ trợ chủ động” sửa các lỗi thường gặp, và năm cuối là “hỗ trợ bảo mật”. Sau khi kết thúc chu kỳ 3 năm, phiên bản PHP đó sẽ không được hỗ trợ nữa.

Bạn có thể tìm những phiên bản PHP hiện đang được hỗ trợ tại đây.Tại thời điểm viết bài PHP 5.5 được hỗ trợ bảo mật, PHP 5.6 và 5.7 đang được “hỗ trợ chủ động”.

Sửa thử một lỗi

Trước khi biểu diễn một workflow căn bản, hãy giải quết bug #71635 trên bugs.php.net. Bug report có chỉ ra segfault khi gọi DatePeriod::getEndDate() khi không có date. Vì vậy, việc đầu tiên ta cần làm là xác nhận tính hợp lệ.

Với các lỗi nhỏ (ít hoặc không yêu cầu thiết lập môi trường), đầu tiên ta sẽ xem thử có thể dùng 3v4l tái tạo lại bug được không (3v4l là một công cụ cho phép ta chạy những đoạn code nhỏ trên hàng trăm phiên bản PHP khác nhau). Việc này sẽ cho phép ta xác đinh nhanh chóng liệu các phiên bản PHP cũ hơn (nhưng vẫn được hỗ trợ) có bị ảnh hưởng hay không. Như ta thấy, PHP cho ra segfault cho tất cả phiên bản từ 5.6.5 đến 7.0.4.

Ta sẽ luôn phải tái tạo bug cục bộ trước khi fix (dù có dùng được 3v4l hay không). Bạn sẽ phải fork php/php-src và clone fork cục bộ. Nếu bạn đã thực hiện bước này từ trước, hay update clone, và truy lại tất cả các release được tag gần nhất (có git remote update).

Ta sẽ làm việc với nhánh PHP 5.6 trước vì đó là phiên bản thấp nhất bị ảnh hưởng (đồng thời vẫn được “hỗ trợ chủ động”). (Nếu bug này ảnh hưởng đến PHP 5.5, ta sẽ vẫn không dùng phiên bản này và làm việc với PHP 5.6 vì bug này không liên quan đến bảo mật). Workflow chuẩn khi kiểm tra fix lỗi là tập trung fix vào phiên bản thấp nhất (nhưng vẫn còn được hỗ trợ) bị ảnh hưởng. Một developer của php/php-src sẽ gộp fix này nếu cần thiết.

Vậy thì, hãy checkout một copy của nhánh PHP 5.6:

Kế đến, chúng ta sẽ build PHP và cố gắng tái tạo cụ bộ segfault bằng cách tạo một file (như segfault.php) code sau:

Tiếp đó, ta sẽ chạy segfault.php PHP binary vừa build:

(cờ -n có nghĩa rằng file php.ini sẽ không được dùng cho cấu hình, từ đó chặn load of errors mỗi khi bạn thực thi một file với PHP binary cục bộ. Điều này sẽ rất tiện nếu bạn load các extension (phần mở rộng) tùy chỉnh vào file .ini mặc định.)

Một khi đã xác định có thể kích hoạt cục bộ, ta có thể tạo một test. Hãy gọi file test này là bug71635.phpt và đặt nó vào thư mục ext/date/tests/ với các nội dung sau:

Chúng ta giờ đây sẽ chạy một debugger (tự chọn) lên file segfault.php đã tạo trước đó. (Tôi dùng LLDB tích hợp chung với Mac OS X, nhưng GDB là một debugger tương tự có hỗ trợ lệnh overlap.)

(Lần này, lệnh -n không được dùng đến để tránh xung đột với lldb.)

Sau khi vào LLDB debugger, ta gõ run để thực thi file. Bạn có thể thấy rõ segfault xảy ra ở đâu trong đoạn code dưới đây:

Tuy frame đầu không cho thấy quá nhiều thông tin (trừ khi bạn lập trình trên asm), bạn vẫn có thể thấy chưng trình ngừng vì EXC_BAD_ACCESS. Ta cũng có thể thấy pointer address dự định được sử dụng là 0x0 (null).

Dùng lên bt cho ta thấy backtrace của segfault (mỗi frame dẫn đến segfault). Nhìn vào frame #1 (bằng cách nhập frame select 1), ta đã quay lại C code và có thể thấy được dòng code gây ra vấn đề:

Từ đây, ta có thể thấy thử phạm là biến dpobj->end bị null, từ đó cố tham chiếu ngược nó, gây segfault. Vì vậy, ta sẽ đặt một check phía trên để xem liệu dpobj->end có phải là null pointer hay không, nếu đúng, ta chỉ việc quay lại từ hàm này (càng sớm càng tốt):

Hoàn toàn quay lại từ một method sẽ khiến cho hàm trả kết quả null (giống như kết quả thất bại của mọi hàm internal PHP khác). Điều này sảy ra vì biến return_value (truy cập được trong mọi hàm definition) giữ giá trị thật sự được trả về (được mặc định thành null).

Vậy hãy build PHP và chạy lại test:

Bạn giờ sẽ có thể qua test. Giờ đây ta đã có thể ủy thác file được update và test lỗi tương ứng, và rồi đăng PR với nhánh php/php-src 5.6.

Kết

bài viết này đã mô tả một workflow đơn giản được dùng khi gỡ lỗi trong core. Gỡ lỗi yêu cầu rất ít kiến thức C và là một bước khởi điểm tuyệt vời trước khi tìm hiểu chuyên sâu hơn về.

Sửa lỗi cũng là một thách thức mới lạ với các bạn đã quá quen thuộc với các thách thức trong thuật toán (bạn có thể tìm hiểu thêm tại Project Euler). Và với kho bug lên đến con số 5000, có khi bạn xem hoài cũng không hết!

CHIA SẺ