# Sửa lỗi không tìm được vị trí để sắp xếp nhân hệ thống (Fixing KASLR slide values)

Phần này dành cho những bạn muốn tìm hiểu nguyên nhân và sửa lỗi "Couldn't allocate runtime area" (Không thể dành riêng vùng nhớ để khởi động hệ thống macOS). Lỗi này thường xuyên xuất hiện nhất trên các dòng Z390, X99 và X299. Bạn không bị cũng nên đọc để biết nha, phần này giải thích về cách thức triển khai bảo mật trong bộ nhớ hay lắm đó. Phần này khá dài và có nhiều đoạn cần phải đọc kỹ lại nhiều lần thì bạn mới hiểu vấn đề, bạn cứ từ từ mà đọc rồi học cách sửa nhé, không ai hối bạn đâu nè.

  • Lưu ý: OpenCore là yêu cầu bắt buộc, Clover không còn được hỗ trợ trong bài viết này

# Vậy KASLR là cái quỉ ma gì đây?

Thì nó là vậy đó 😃 Giỡn thôi, KASLR là viết tắt của Kernel address space layout randomization (Ngẫu nhiên hóa sơ đồ không gian địa chỉ nhân hệ thống). Mục đích chính của nó là để bảo mật. Cụ thể, nó làm cho những kẻ tấn công khó xác định được các đối tượng quan trọng (thường là Kernel) nằm ở đâu trong bộ nhớ vì vị trí của tụi nó luôn thay đổi ngẫu nhiên giữa các máy khác nhau và giữa mỗi lần khởi động chứ không cố định. Đại loại là những đứa hacker muốn hack vào máy sẽ phải biết đường thâm nhập các thành phần quan trọng của hệ điều hành đang nằm trong bộ nhớ RAM để tấn công. Giả dụ nó biết rồi, thì ở lần khởi động tiếp theo nó mới tấn công được vào ngay đó. Tuy nhiên mỗi lần khởi động là 1 lần thay đổi vị trí vùng nhớ, giống như cùng 1 cái cửa đó nhưng mỗi lần bạn mở máy là thay đổi 1 loại ổ khóa khác với chìa khác cho cánh cửa vậy, khiến cho việc tấn công trở nên khó khăn vì Hacker bây giờ không biết cánh cửa xài chìa loại gì. KASLR là cái làm khó và gửi thông điệp cho thằng Hacker là "Hack hack cái conmemay, tuổi l** hack được tao nhé, bỏ cuộc đê". Đọc thêm Bài giải thích chi tiết hơn về KASLR (opens new window) để hiểu rõ hơn nhé.

Vấn đề phát sinh khi bạn gắn quá nhiều phần cứng; mỗi thiết bị như vậy có bản đồ bộ nhớ (memory map) quá nhỏ khiến việc chia bộ nhớ cho tụi nó bị lung tung khắp nơi không có trật tự (thiệt ra là do firmware của mấy ông nhà sản xuất viết quá tệ trong việc phân chia bộ nhớ do quen việc Windows linh hoạt chạy sao cũng được nên cũng không cần mắc công tối ưu phần này làm gì). Lúc này, bộ nhớ RAM của bạn giống như một cái bãi đậu xe bị mấy ông nội xe máy (đóng vai phần cứng) đậu một cách vô tội vạ, hổng còn một khoảng trống nào đủ bự và liền mạch để nhét nguyên chiếc xe tải (Kernel) vô hết. Có thể tổng diện tích còn trống thì nhiều đó, nhưng nó cứ lẻ tẻ mỗi nơi một chút, hông có chỗ nào đủ diện tích để Kernel đậu vừa.
Vì macOS là công tử bột đã quen chạy trên phần cứng cao cấp của Apple, khi mình ép macOS qua ở nhà PC (Hackintosh), nó bị "sốc văn hóa" vì bãi xe quá lộn xộn cũng như Kernel không được thiết kế để xé nhỏ dữ liệu, hoặc dạt ra những vùng nhớ xa xôi rồi tự động ghép lại khi cần thiết như Windows, nó đòi hỏi phải có một khoảng trống thiệt bự và liền mạch thì nó mới chịu đậu vào.
Trên firmware của Apple phần thuật toán KASLR được chăm chút rất kỹ càng và thông minh, nó tự biết đẩy hết vùng nhớ phần cứng sang một bên, sắp xếp cho gọn gàng để chừa một vùng nhớ rộng thênh thang cho macOS tha hồ chạy, do đó khi chạy trên Hackintosh có firmware "không được thông minh như vậy" nó hổng biết đường nào mà né phần cứng nên nó đành treo máy luôn. Đây là lúc giá trị slide=xxx phát huy sở trường. Thay vì để macOS chọn đại một vùng ngẫu nhiên mỗi khi boot, chúng ta sẽ tìm cho nó 1 vùng trống mà chúng ta biết chắc là không có phần cứng nào sử dụng vùng nhớ đó để chạy ngon lành (bắt chước cách firmware của Apple đang làm).

# Và ai cần đọc cái này

Như mình đã đề cập trước đó, bài viết này dành cho những bạn gặp vấn đề không có đủ dung lượng cho nhân hệ điều hành (đừng có hiểu theo nghĩa máy bạn thiếu RAM nha, không phải đâu) hoặc kernel chuyển đến một vị trí vùng nhớ quá nhỏ dẫn đến cái lỗi ngặt nghèo trên. Bạn thường sẽ gặp lỗi tương tự như vầy khi khởi động:

Error allocating 0x1197b pages at 0x0000000017a80000 alloc type 2 (Lỗi khi cố gắng lấy 72.059 ô nhớ tại địa chỉ cụ thể)
Couldn't allocate runtime area (Không thể dành riêng vùng nhớ để khởi động hệ thống macOS)

Giải thích lỗi trên cho bạn hiểu:

  • 0x1197b pages: chuyển sang số thập phân là 72.059 ô nhớ mà macOS cần để chạy.
  • "at 0x0000000017a80000" là vị trí địa chỉ cụ thể trong bộ nhớ mà macOS yêu cầu nó phải nằm ở đó.
  • Vấn đề ở đây là macOS không chỉ đòi chạy trên ô nhớ đó, mà nó còn đòi phải nằm ngay đúng vị trí địa chỉ bộ nhớ đó. (Thích làm ông nội người ta mà)
    • Để giải thích cho bạn dễ hiểu, ví dụ macOS là một khách hàng khó tính muốn cất một căn nhà diện tích 72.059 cm² ở địa chỉ đường 0x0000000017a80000. Nó thích như vậy và bạn phải đáp ứng yêu cầu của nó. Tuy nhiên vấn đề bắt đầu xảy ra khi ô nhớ đó đã có thiết bị khác cất nhà lên đó trước (tức là đã giành ô nhớ đó để chạy). Nó từ chối khởi động và báo lỗi trên.

Hoặc một biến thể lỗi khác như:

Only 244/256 slide values are usable! (Lỗi giới hạn vị trí bộ nhớ ngẫu nhiên, chỉ có 244 trên tổng số 256 vị trí ngẫu nhiên là có thể sử dụng!).

Giải thích lỗi trên cho bạn hiểu:

  • Như mình đã giải thích về KASLR ở trên, macOS có 256 vị trí (từ 0 đến 255) để "ẩn náu" trong RAM nhằm bảo mật. Vấn đề ở đây là lỗi này báo rằng có 12 vị trí đã bị phần cứng chiếm dụng hoặc bị lỗi. Càng ít vị trí trống (ví dụ chỉ còn 50/256), tỉ lệ bạn khởi động máy thành công càng thấp. Nếu con số này quá nhỏ, macOS sẽ không tìm được chỗ trốn và treo máy ngay lập tức.

Thậm chí là đang xài macOS ngon lành cũng tự nhiên lăn đùng ra panic (lỗi sập hệ thống), lỗi chi tiết như sau:

panic(cpu 6 caller 0xffffff801fc057ba): a freed zone element has been modified in zone kalloc.4096: expected 0x3f00116dbe8a46f6 but found 0x3f00116d00000000 (Sụp đổ hệ thống: Một vùng nhớ đã được giải phóng nhưng bị chỉnh sửa trái phép. Hệ thống yêu cầu vị trí bộ nhớ 0x3f00116dbe8a46f6 nhưng lại bị chuyển hướng đến địa chỉ 0x3f00116d00000000)

Giải thích lỗi trên cho bạn hiểu:

  • Lỗi này xảy ra khi macOS đang hoạt động bình thường, nó có một vùng nhớ vừa mới xài xong nên trả lại cho hệ thống (còn kêu là 'giải phóng bộ nhớ'). Theo quy tắc bảo mật của Apple, cái vùng nhớ đó dù đã được trả nhưng tạm thời phải được giữ nguyên hiện trạng (được bảo vệ nghiêm ngặt nha), hổng ai được đụng vô hết (Cơ chế "vùng đệm"). Nó sẽ gắn một cái "niêm phong" (cái mã expected mà bạn thấy ở dòng thông báo lỗi đó) vào trong vùng nhớ. Một mặt là chiêu trò của Apple "thả con tép để bắt con tôm" chống lỗ hổng Use-After-Free, đây là lỗ hổng mà hacker rất khoái. Khi một chương trình vừa xài xong vùng nhớ và trả lại (free), hacker sẽ lẹ tay nhét mã độc vô đúng cái chỗ vừa trống đó. Nếu macOS quay lại xài tiếp vùng nhớ này mà không kiểm tra, nó sẽ vô tình thực thi luôn cái mã độc của hacker.
    Nếu cái niêm phong còn y nguyên, nghĩa là vùng nhớ 0x3f00116dbe8a46f6 được đánh dấu là sạch, hệ thống sẽ yên tâm cấp nó cho đứa khác xài. Còn nếu niêm phong bị rách (bị sửa thành địa chỉ bộ nhớ 0x3f00116d00000000), macOS nhận ra địa chỉ mà nó đã niêm phong đã bị phá hoại, nó cho rằng đây là hành vi bất thường (là bị chỉnh sửa trái phép), là vấn đề bảo mật (SOS báo động đỏ) và sẽ cho sập hệ thống ngay lập tức để ngăn ngừa hư hỏng dữ liệu; dù thiệt ra nguyên nhân là do một phần cứng bất kỳ nào đó trong máy bạn đã vô tình được cấp vùng nhớ vào khu vực vùng nhớ trống đã được macOS niêm phong (vì lỗi KASLR vẫn còn đó đã sửa đâu); nên việc cấp phát bộ nhớ trên macOS bị loạn đao pháp 😂 đang nghe nhạc hay mà sập máy thì cũng cay cú thiệt á.

Cái oái oăm của mấy lỗi này là nó rất ngẫu nhiên. Có khi bạn tắt máy, rồi mở đi mở lại 20 lần thì lại boot được bình thường, nhưng đó chỉ là giải pháp tạm thời thôi.

Sự thật thú vị: Hệ thống mất khoảng 31 ms để tìm một vùng hoạt động, việc thiết lập giá trị slide thủ công có thể giúp giảm thời gian khởi động thêm khoảng 0.207% đó (giỡn vui là chính thôi chứ không đáng kể lắm đâu)!

# Vậy sửa cái lỗi chết tiệt này như thế nào?

Thiệt ra cách sửa khá đơn giản. Bạn sẽ cần:

Và chúng ta cần cấu hình file config.plist -> Booter -> Quirks như sau:

  • AvoidRuntimeDefrag: YES
    • Sửa các dịch vụ UEFI như ngày giờ, NVRAM, điều khiển nguồn sao cho giống với cách máy Mac hoạt động.
  • DevirtualiseMmio: YES
    • Dọn dẹp địa chỉ bộ nhớ bị chiếm dụng nhưng không được sử dụng, mở rộng tùy chọn cho phép nhập giá trị slide=N, rất hiệu quả trong việc sửa lỗi đặc trưng Không thể dành riêng vùng nhớ trên Z390.
  • EnableSafeModeSlide: YES
    • Cho phép dùng giá trị slide trong chế độ An toàn (Safe Mode)
  • ProtectUefiServices: NO
    • Bảo vệ các dịch vụ UEFI không bị firmware ghi đè, thường dùng cho máy ảo hoặc dòng Ice Lake/Comet Lake. Tuy nhiên trong trường hợp này thì ta tắt nó đi.
  • ProvideCustomSlide: YES
    • Đảm bảo kernel chỉ chọn các vùng bộ nhớ tốt. Nó vẫn ngẫu nhiên nhưng loại bỏ mấy vùng "tào lao" gây lỗi boot.
  • RebuildAppleMemoryMap: YES
    • Tạo bản đồ bộ nhớ tương thích với macOS, có thể gây lỗi khởi động trên một số firmware OEM của laptop. Vì vậy nếu bạn gặp lỗi khởi động sớm, hãy tắt chức năng này. Điều này đảm bảo bản đồ bộ nhớ của chúng ta sẽ phù hợp với những gì nhân hệ điều hành mong muốn.

# Chuẩn bị phần BIOS

Lý do chúng ta cần đặt lại bản đồ bộ nhớ (memory map) là vì chúng ta muốn nó đi vào nề nếp trước. Cụ thể ý tưởng của mình là điều chỉnh làm sao khi máy khởi động sẽ có ít sự biến động trong bộ nhớ hơn (bớt bớt nhảy múa lung tung), từ đó chúng ta hạn chế tối đa các lỗi 'hên xui' khó chịu (vì bản đồ bộ nhớ bình thường có bao giờ đồng nhất giữa các lần mở máy đâu). Nhưng cái này không có nghĩa là vô hiệu hóa luôn tính năng bảo mật KASLR nhé.
Thay vì để bộ nhớ tự tung tự tác muốn chia sao chia, còn macOS 'nhắm mắt chọn đại' một vị trí ngẫu nhiên rồi hên xui đụng đầu vô phần cứng, mình sẽ chủ động đi tìm một khoảng trống bự nhất và an toàn nhất trên bản đồ bộ nhớ. Sau đó mình chỉ định macOS: 'Nè, ông đáp vô đúng cái chỗ này cho tui nè, chỗ này tui coi kỹ rồi, hổng có ông nội phần cứng nào chiếm ở đây hết, chạy bao ngon! Để chuẩn bị, bạn thực hiện các bước sau:

  • Cập nhật BIOS trước đi (cái này cực kỳ quan trọng vì các bản BIOS đời đầu thường bị lỗi memory map, nhất là Z390.)
  • Xóa CMOS (Tức là reset BIOS bằng cách tháo pin CMOS rồi gắn lại)
  • Mở các thiết lập BIOS cần thiết:
    • Above4GDecoding: Cho phép các thiết bị sử dụng vùng nhớ trên 4GB, giúp macOS có thêm "đất diễn" vì các thiết bị cần vùng nhớ lớn như card màn hình đang cố gắng chen vô MMIOL vốn đã ít ỏi sẽ tự động chuyển lên MMIOH giúp giải phóng không gian bộ nhớ để chứa Kernel, tuy nhiên nó cũng có thể gây ra sự cố khác trên dòng board X99/X299. Chi tiết về MMIO bạn đọc ở dưới.
      • Nếu bạn gặp sự cố, bảo đảm là cài đặt "MMIOH Base" (Memory Mapped I/O High - Vùng nhớ hỗ trợ giao tiếp phần cứng tầng cao) được cài đặt tối đa ở mức 12 TB hoặc thấp hơn, vì macOS chỉ hỗ trợ địa chỉ vật lý tối đa là 44-bit (về lý thuyết là hỗ trợ tối đa 16 TB - tuy nhiên vì nhiều lý do ổn định mình khuyên bạn nên giới hạn ở mức 12 TB thôi); chỉnh cao quá nó không 'với' tới được đâu.
        • Giải thích thêm một chút về tính năng MMIO (cơ chế cấp phát bộ nhớ để giao tiếp với thiết bị ngoại vi), đây là phương thức mà CPU sử dụng để "nói chuyện" với các linh kiện khác (như Card đồ họa, Card mạng). Bạn cần hiểu CPU không chạy đi gửi dữ liệu cho từng linh kiện, thay vào đó nó sẽ gán cho mỗi linh kiện một cái địa chỉ bộ nhớ riêng biệt. VD: CPU muốn gửi dữ liệu cho Card màn hình, nó chỉ cần viết vẽ ra dữ liệu rồi gửi vào "địa chỉ" đó là xong. Vì nó là cơ chế dùng địa chỉ bộ nhớ để nói chuyện, nên nó phải "xí" một phần diện tích trên bản đồ hệ thống. Có 2 loại MMIO là MMIOL - L là viết tắt của Low và MMIOH - H là High như đã được giải thích ở trên.
          • MMIOL là các địa chỉ ở tầng thấp (tương ứng dưới 4GB). Đây là vùng đất chật chội vì các thiết bị/phần cứng đời cũ thường tranh nhau ở đây. Nếu bạn không mở Above4GDecoding thì gần như các thiết bị sẽ tranh hết bộ nhớ mà không còn chỗ trống nào cho macOS chạy nữa (chính là nguyên nhân bạn vô đây đọc bài hướng dẫn này nè).
          • Ngược lại, MMIOH là các địa chỉ ở tầng cao (trên 4GB). Các thiết bị đời mới, hoặc những thiết bị cần băng thông lớn như card LAN; đặc biệt là những loại Card đồ họa khủng (có VRAM lớn) cần những không gian cực kỳ rộng rãi ở các "tầng cao" này để làm việc.
      • Lưu ý: Với các BIOS hỗ trợ Resizable BAR Support (tính năng hỗ trợ thanh ghi có thể thay đổi kích thước), mở Above 4G sẽ mở khóa chức năng đó. Bạn vui lòng bảo đảm Booter -> Quirks -> ResizeAppleGpuBars đã được đặt 0 nếu cài đặt trên được mở.
    • Kiểm tra Boot Options -> Chế độ Windows 8.1/10: Thiết lập này giúp chặn đứng mấy thứ 'rác rưởi' từ thời đồ đá (Legacy) bị nạp vào hệ thống. Sự thật thú dzị, cái tính năng other OS (Hệ điều hành khác) thiệt ra được thiết kế để chạy mấy bản Windows cổ lỗ sĩ thôi, chứ không phải dành cho 'hệ điều hành khác' (như macOS hay Linux) đâu, đừng để nó lừa!"
  • Vô hiệu hóa các thiết bị/phần cứng không sử dụng trong BIOS (chi tiết hơn là nó giúp bản đồ bộ nhớ (Memory Map) bớt 'nhảy múa' lung tung mỗi lần khởi động, càng ít thiết bị thì đồng nghĩa có nhiều chỗ trống hơn; dù bản đồ bộ nhớ vẫn lộn xộn tuy nhiên vùng nhớ trống bị xé lẻ trước đây giờ sẽ thành 1 cục siêu bự, giúp giảm thiểu tỉ lệ lỗi Boot). Danh sách các thứ bạn nên "tiễn vong":
    • CSM: Chế độ hỗ trợ phần cứng cũ. Nó lôi theo một đống rác không ai cần, đôi khi còn làm hư luôn giao diện Shell khiến bạn không thể boot vào được.
    • Intel SGX (Intel Software Guard Extensions): Tính năng bảo mật của Intel. Không có tích sự gì trong macOS mà còn chiếm một đống diện tích bộ nhớ. macOS nói không với giải pháp bảo mật cây nhà lá vườn của Intel.
    • Parallel Port (Cổng song song): macOS thậm chí còn không biết cái cổng này tồn tại trên đời.
    • Serial Port (Cổng nối tiếp): Trừ khi bạn là kỹ sư đang cần tìm lỗi trong nhân hệ điều hành (debug kernel), còn không thì tắt nó đi cho rảnh nợ.
    • iGPU: Không hẳn là phương án tối ưu, nhưng có những trường hợp mà bản đồ bộ nhớ 'phình to' đến mức iGPU không còn chỗ mà chen chân vào nữa (nên phải tắt đi để nhường chỗ cho cái khác)...
    • Thunderbolt: Đa số máy Hackintosh không chạy được Thunderbolt ổn định. Những mainboard vốn không có cổng này mà vẫn để mở trong BIOS thì chỉ lãng phí diện tích bộ nhớ thôi.
    • Đèn LED (LED lighting): Chia buồn nhé bạn hiền, đến lúc phải chia tay ánh đèn RGB để ưu tiên cho hệ thống ổn định rồi.
    • Giả lập USB đời cũ (Legacy USB): Lại thêm một đống rác rưởi của di sản thời "đồ đá" để lại. Tắt và tắt!

Có mấy ông hỏi: 'Ê, làm vậy rồi hacker nó biết macOS nằm đâu nó vô nó hốt xác mình sao?

  • Đây là một câu hỏi rất hay. Thực tế là bạn phải chấp nhận mất 1 trong 2, 1 là bảo mật tuyệt đối nhưng khỏi xài macOS luôn, 2 là chấp nhận lớp bảo mật bị mỏng đi nhưng khởi động được vào macOS ngon lành; chứ bạn không thể có được cả 2 vì hạn chế trong firmware do các hãng sản xuất viết (không thể bắt chước hành vi phần cứng giống máy Mac thiệt 100%). Thực tế là KASLR vẫn hoạt động, chỉ có macOS là được gán vào địa chỉ cố định, tức là chỗ đậu xe đó là chỉ dành cho nó không được có xe nào đậu hết. Các phần cứng khác nếu được cấp địa chỉ vùng nhớ thì tụi nó vẫn được nhảy múa bình thường mà không sợ đụng trúng Kernel.
  • Tuy nhiên bạn cũng đừng quá lo lắng vì cái slide mình gán nó giống như mình chỉ cho macOS một cái 'khu vực' an toàn để nó đáp xuống thôi. Ví dụ đi, bạn chỉ cho macOS một cái quận (ví dụ Quận 1) để nó vô đó định cư vậy đó. Còn nó định cư ở nhà nào là chuyện của nó. Trong cái quận đó có mấy chục ngàn cái căn nhà, dù hacker có thể biết bạn ở Quận 1 thiệt, nhưng cụ thể bạn (đóng vai kernel) ở số nhà nào, tầng mấy, đường gì thì còn khuya nó mới biết. Vì bên trong cái vùng đã chọn đó, macOS vẫn thực hiện 1001 lớp bảo mật khác để xáo trộn các thành phần mô-đun nên không dễ hack vậy đâu.
  • Apple rất nổi tiếng về bảo mật cực đoan nên họ làm sao mà để hớ hênh 1 lỗ hổng cơ bản như vậy được. Dạng như chúng ta mở 1 cánh cửa làm mồi nhử để chọc tức Hacker thôi. Với lại, mình là người dùng cá nhân, hổng phải mục tiêu của mấy vụ tấn công tầm cỡ quốc gia đâu mà lo hacker nó rình rập cái địa chỉ kernel của mình dữ vậy. Mà đã là tầm cỡ quốc gia thì không ai xài Hackintosh đâu 😃 Quan trọng là mình boot vô được máy cái nha!

Có câu hỏi thú vị hơn: "Nếu Firmware một số hãng viết tệ hại làm chia bộ nhớ lộn xộn không đâu vào đâu, vậy tại sao ta vẫn tìm được chỗ trống để chen vô cho macOS hay vậy?"

  • Thiệt ra là nhờ vào quy luật "Quy hoạch bộ nhớ" đó bạn! Dù Firmware có "ngáo" tới đâu, nó vẫn phải tuân thủ cái bản đồ gọi là Chipset Memory Map của kiến trúc x86 để máy tính có thể chạy được. Có hai cái đặc điểm cực kỳ quan trọng giúp macOS có "đất diễn":

    • "Vùng đất vàng" 0x100000 (1MB đầu tiên): Trong kiến trúc máy tính, đây gọi là khu vực địa chỉ bộ nhớ thấp (Low Memory). Thời "đồ đá", máy tính chỉ có vài trăm KB RAM nên mọi thứ quan trọng đều nằm ở đây. Ngày nay, dù RAM của bạn có tới 64GB, thì 1MB đầu tiên này vẫn được giữ lại như một cái "di tích lịch sử" để nạp mấy thứ căn bản lúc vừa bấm nút nguồn. Chính vì nó là khu vực "đời cũ" và chật hẹp, nên mấy thiết bị hiện đại như Card đồ họa (GPU) hay Card mạng (LAN) nó chê, hổng thèm đóng đô ở đây. Tụi nó thích nhảy lên mấy "vùng cao" (MMIOH) để ở cho thoải mái hơn vì cơ bản tụi nó cũng quá là phình to rồi, nhét vô cái 1MB đó chật chội lắm.
    • Quy luật "Nước sông không phạm nước giếng": Kể cả khi firmware được viết dở tệ, các hãng sản xuất vẫn phải chia rõ ràng: "Khúc này là RAM vật lý (để chạy App, chạy Win), khúc kia mới là địa chỉ thiết bị (MMIO)". Thường thì các thiết bị PCI sẽ được "đẩy" lên những vùng địa chỉ cực cao hoặc nằm ở MMIOL nhưng có khoảng hở cố định. Do đó, trên RAM vật lý của bạn lúc nào cũng có những đại lộ thênh thang (thường bắt đầu ngay sau 1MB đầu tiên) mà hông có thiết bị phần cứng nào được phép bén mảng tới. Đây còn gọi là vùng xám đó. Đó là lý do vì sao khi tính toán slide, bạn sẽ thấy mấy vùng như 0x100000 hay 0x200000 nó luôn trống trải và an toàn để macOS "đáp" xuống (Lưu ý mỗi máy sẽ khác nhau, đây chỉ là ví dụ).
  • Vậy tại sao vẫn có máy bị lỗi dù đã tuân theo tiêu chuẩn? Nếu "đại lộ" luôn có chỗ trống, tại sao mấy con Z390 hay X299 lại hay bị lỗi Couldn't allocate? Như mình đã kể trước đó là do Firmware (BIOS) của mấy dòng này chơi hệ... "bày hầy":

    • Rải mìn khắp bản đồ bộ nhớ: Thay vì gom mấy cái địa chỉ thiết bị vô một góc cho gọn, mấy ông kỹ sư lại rải mấy cái gọi là UEFI Runtime Services (mấy cái dịch vụ chạy ngầm của BIOS) nằm rải rác như "rải mìn" ngay giữa những chỗ mà hệ thống KASLR của macOS định đáp xuống.
    • Chòi lá chắn đường: Mấy cái dịch vụ này không phải là phần cứng, nhưng nó thích "cắm trại" giữa lộ. Khi KASLR tung xúc xắc, nếu hên thì nó trúng "đại lộ", xui cái là nó nhảy ngay trúng cái "chòi lá" của mấy dịch vụ BIOS này, vậy là gây treo máy.
    • Giải pháp "xây cầu vượt": Đó là lý do chúng ta phải bật Above4GDecoding. Nó giống như xây thêm một cái cầu vượt ở tầng cao để lôi hết mấy ông nội phần cứng lên đó, trả lại cái "đại lộ" dưới thấp thiệt là trống trải cho macOS thoải mái nhảy múa mà hông sợ đụng trúng ai.
  • Một sự thật "ngang ngược": Tại sao máy tui tận 64GB RAM mà macOS cứ đòi chen chúc ở tầng thấp?

    • Đọc tới đây chắc nhiều bạn sẽ thắc mắc: "Ủa, máy tui RAM bự chà bá, tầng cao (trên 4GB) trống huơ trống hoác sao macOS hông leo lên đó mà nằm cho khỏe, cứ đòi chen vô cái tầng thấp (MMIOL) chật chội này làm chi rồi báo lỗi?" Câu trả lời nằm ở cái tính "bảo thủ" của Apple trong thiết kế hệ điều hành:
    • Thói quen của mấy đứa "nhà giàu": Trên máy Mac xịn, Apple nắm quyền sinh sát phần cứng. Họ luôn dọn sẵn cái "sảnh tầng trệt" (vài trăm MB đầu tiên của RAM) sạch bong xà bông cho Kernel đáp xuống. Vì tầng trệt đã quá ngon rồi, nên Kernel macOS từ xưa tới giờ hông có khái niệm phải "leo lầu" (lên vùng RAM cao) để khởi động làm chi cho tốn công.
    • Giới hạn của KASLR: Như mình đã phân tích, macOS chỉ có tối đa 256 giá trị slide. Mỗi giá trị tương ứng với 2MB bộ nhớ. Làm một phép tính đơn giản: 256 * 2 = 512. Điều này có nghĩa là vùng đất mà Kernel có thể "ngẫu nhiên hóa" vị trí chỉ loanh quanh trong khoảng 512MB đầu tiên của thanh RAM mà thôi. => Kết luận: Dù máy bạn có 128GB hay 1TB RAM đi nữa, cái "số phận" của macOS lúc khởi động vẫn chỉ nằm gọn trong vài trăm MB đầu tiên đó. Đây chính là lý do vì sao mình phải cực khổ cố gắng sửa cái lỗi KASLR này. Nó giống như chuyện bạn đuổi hết mấy ông nội phần cứng "đô con" (như Card đồ họa 8GB, 12GB) leo lên sân thượng (MMIOH - vùng nhớ trên 4GB) mà ngồi. Có như vậy, cái sảnh tầng trệt (dưới 4GB) mới đủ thông thoáng cho "công tử" macOS đi dạo. Nếu bạn đóng cửa sân thượng (tắt Above4G), đám phần cứng đó sẽ tràn xuống tầng trệt, chiếm hết chỗ của macOS và gây ra cái lỗi "Không thể dành riêng vùng nhớ" đó!

Giờ bạn đã hiểu gốc rễ và nguyên nhân, chúng ta cùng cố gắng sửa cho nó chạy được nha.

# Boot thử sau khi điều chỉnh

Sau khi chỉnh sửa EFI, config và BIOS, hãy thử boot lại. Nếu vẫn lỗi, chúng ta phải "đi lặn" sâu hơn để tính toán giá trị slide thủ công. Xem thêm ở dưới nếu bạn rơi vô trường hợp thứ 2 nha.

# Cách tìm giá trị Slide

Bạn hãy mở EFI Shell từ menu boot và gõ lệnh memmap. Nó sẽ hiện ra một danh sách dài dằng đặc các trang bộ nhớ. Và đây là lúc phần thú vị nhất bắt đầu.

Ví dụ về bảng bạn sẽ thấy:

Type (Loại bộ nhớ) Start (Địa chỉ bắt đầu) End (Địa chỉ kết thúc) # Pages (Vùng nhớ) Attributes (Thuộc tính)
RT_Data 0000000000000000 0000000000000FFF 0000000000000001 800000000000000F
Available 0000000000001000 0000000000057FFF 0000000000000057 000000000000000F
Reserved 0000000000058000 0000000000058FFF 0000000000000001 000000000000000F
Available 0000000000059000 000000000008FFFF 0000000000000037 000000000000000F
RT_Code 0000000000090000 0000000000090FFF 0000000000000001 800000000000000F
Available 0000000000091000 000000000009DFFF 000000000000000D 000000000000000F
Reserved 000000000009E000 000000000009FFFF 0000000000000002 000000000000000F
Available 0000000000100000 000000005B635FFF 000000000005B536 000000000000000F
BS_Data 000000005B636000 000000005B675FFF 0000000000000040 000000000000000F
Available 000000005B676000 000000006AF77FFF 000000000000F902 000000000000000F
LoaderCode 000000006AF78000 000000006B155FFF 00000000000001DE 000000000000000F
BS_Data 000000006B156000 000000006B523FFF 00000000000003CE 000000000000000F
ACPI_NVS 000000006B524000 000000006B524FFF 0000000000000001 000000000000000F
BS_Data 000000006B526000 000000006B625FFF 0000000000000100 000000000000000F
Available 000000006B626000 000000006B634FFF 000000000000000F 000000000000000F

Giờ bạn sẽ tự hỏi làm thế nào để chúng ta có thể chuyển đổi giá trị này thành giá trị slide, thiệt ra rất đơn giản. Điều chúng ta quan tâm là giá trị lớn nhất có sẵn trong cột Start. Trong ví dụ này, chúng ta thấy 000000006B626000 là giá trị lớn nhất, lưu ý rằng đây là giá trị định dạng thập lục phân, vì vậy nếu bạn thấy có nhiều giá trị gần giống vầy, bạn cần chuyển đổi hết tụi nó sang hệ thập phân. Để tính giá trị slide (app máy tính tích hợp trong macOS có chức năng lập trình bằng cách nhấn ⌘+3):

000000006B626000 = 0x6B626000

(0x6B626000 - 0x100000)/0x200000 = 0x35A

Và nhớ kiểm tra lại để coi nó chính xác chưa nha:

0x100000 + (0x35A * 0x200000) = 0x6B500000

Nếu kết quả trả về không khớp số ban đầu (0x6B500000 so với 0x6B626000), bạn hãy +1 vào giá trị slide cuối cùng. Lý do không khớp là do máy tính tự động làm tròn. Lấy một ví dụ khác là 0x35A chuyển đổi sang thập lục phân sẽ có giá trị 858 và sau đó +1 bạn sẽ có slide=859.

Nhưng KHOAN, dừng lại khoảng chừng là 2 giây... Ủa cái này lớn hơn 256 mà!

Đúng rồi, đó là do bản đồ bộ nhớ của bạn đang mở Above4GDecoding nên nó đi kèm luôn những vùng nhớ ở tầng cao và bạn không thể sử dụng giá trị trên vì macOS chỉ hỗ trợ tới 256 là hết date. Vì vậy, bạn phải dò ngược lên danh sách và tính toán tiếp cho đến khi tìm được giá trị nào đủ lớn nhưng cũng phải cho ra kết quả dưới 256 (với ví dụ của mình là 0000000000100000).

Và để làm rõ hơn về công thức:

(HEX - 0x100000)/0x200000 = Giá trị Slide trong mã HEX

0x100000 + (Giá trị Slide trong mã HEX * 0x200000) phải = Giá trị HEX gốc của bạn (nếu không thì cộng thêm +1 vào giá trị slide của bạn).

Với công thức này, giá trị Start cao nhất mà bạn có thể sử dụng để có được giá trị Slide đủ nhỏ (tức là tính từ giá trị Start phải ra kết quả nhỏ hơn con số 256) sẽ là 0x20100000.

Bây giờ quay lại với file config.plist của bạn, sau đó bạn thêm giá trị slide mà bạn mới vừa tính cùng với mấy cái tham số khởi động khác (như mình là điền slide=0 đó, tại mình tính ra 0x100000). Nếu giá trị này vẫn gây lỗi, bạn thử tiếp giá trị Start bự thứ nhì và cứ vậy mà mần tới cho tới khi hệ thống chạy OK.

Đôi khi bạn có thể thấy rằng khi tính toán giá trị slide, bạn ra được giá trị slide cực nhỏ như slide=-0.379150390625, trong trường hợp này bạn cứ làm tròn slide=0.

Và đối với những bạn đang gặp khó khăn trong việc tìm giá trị slide cho máy của mình, bạn cũng có thể nhập $slide [chèn giá trị #Pages lớn nhất] vào kênh #Sandbox trên r/Hackintosh Discord (opens new window)

Má ơi, cái này khó quá trời luôn, chắc bỏ cuộc quá

Bạn đừng lo lắng quá, vì đã có một giải pháp đơn giản hơn nè. Sau khi chạy lệnh memmap trong shell, hãy chạy tiếp:

shell> gõ lệnh fs0: //đổi lại cái này cho đúng với đường dẫn USB của bạn

fs0:\> gõ lệnh dir //để xác định đã chọn đúng phân vùng, nếu không thì đổi fs1 và cứ vậy mần tiếp

Directory of fs0:\
01/01/01 3:30p   EFI

fs0:\> Gõ lệnh memmap > memmap.txt

Lệnh này sẽ tạo tệp memmap.txt vô đường dẫn ngoài cùng (root) của phân vùng EFI, sau đó bạn có thể thả nó vô r/Hackintosh discord kênh #Sandbox và gõ $slide [Đường dẫn tới file memmap.txt]

# Sử dụng phương pháp DevirtualiseMmio (Dọn dẹp địa chỉ bộ nhớ thiết bị không sử dụng)

Nếu bạn đã thử phương pháp bên trên mà không thành công hãy thử tiếp phương pháp "chuyên trị" này nhé. Cái quirk DevirtualiseMmio này tính ra nó hay ho lắm nghen, đặc biệt là nó giúp mình vượt qua một cái rào cản bự chà bá với mấy hệ thống có nhiều thiết bị PCI (nghĩa là sẽ tạo nhiều vùng nhớ nhỏ lung tung lộn xộn) — tiêu biểu là mấy con main Z390 hay mấy dòng HEDT (máy bàn cao cấp) như X99 và X299. Cách thức hoạt động của nó là vầy: nó sẽ rà soát các vùng MMIO và gỡ bỏ các thuộc tính chạy (runtime attributes) bị chiếm dụng nhưng không được sử dụng, từ đó "dọn chỗ" cho nhân hệ điều hành (kernel) có một không gian rộng rãi, thoải mái để nằm ở trỏng. Khi bạn kết hợp cái này với quirk ProvideCustomSlide, mình vừa giữ được tính năng bảo mật của slide mà vừa giúp máy boot lên ngon lành cành đào luôn.

Đối với mấy hệ thống "khó ở" cực kỳ như Threadripper TRX40 19h, mình cần phải đi kiếm chính xác mấy cái vùng nào mà máy không cần xài để hoạt động bình thường. Đây là lúc mà cái bảng trắng MmioWhitelist xuất hiện để cứu bồ nè. Nhưng mà bạn lưu ý giùm mình cái này: đa số các hệ thống không cần phải làm cái bảng loại trừ (whitelist) này đâu nha.

Nếu bạn đang xài bản Debug của OpenCore mà có mở tính năng DevirtualiseMmio, bạn sẽ thấy mấy dòng này hiện ra trong file log (nhật ký) nè:

21:495 00:009 OCABC: MMIO devirt start
21:499 00:003 OCABC: MMIO devirt 0x60000000 (0x10000 pages, 0x8000000000000001) skip 0
21:503 00:003 OCABC: MMIO devirt 0xFE000000 (0x11 pages, 0x8000000000000001) skip 0
21:506 00:003 OCABC: MMIO devirt 0xFEC00000 (0x1 pages, 0x8000000000000001) skip 0
21:510 00:003 OCABC: MMIO devirt 0xFED00000 (0x1 pages, 0x8000000000000001) skip 0
21:513 00:003 OCABC: MMIO devirt 0xFEE00000 (0x1 pages, 0x800000000000100D) skip 0
21:516 00:003 OCABC: MMIO devirt 0xFF000000 (0x1000 pages, 0x800000000000100D) skip 0
21:520 00:003 OCABC: MMIO devirt end, saved 278608 KB
  • Ghi chú cực kỳ quan trọng: Bạn đọc lại trang Gỡ lỗi OpenCore để biết cách mở tính năng lưu nhật ký (logging) ra file nha.

Như vậy, ở ví dụ trên mình thấy có 6 vùng bộ nhớ cần phải đi kiểm tra coi cái nào "xấu", cái nào "tốt". Cách hay nhất là bạn hãy chặn hết sạch mấy cái vùng MMIO này, trừ ra đúng một cái thôi, rồi thử từng vùng một để lọc ra được danh sách mấy vùng nhớ "ngoan hiền" (vùng tốt).

Bây giờ bạn với mình lấy cái ví dụ ở trên để tự tạo bảng loại trừ MmioWhitelist cho riêng mình nha. Đầu tiên là phải đổi mấy cái địa chỉ từ hệ Thập lục phân (Hexadecimal) sang hệ Thập phân (Decimal) cái nha:

  • MMIO devirt 0x60000000 -> 1610612736
  • MMIO devirt 0xFE000000 -> 4261412864
  • MMIO devirt 0xFEC00000 -> 4273995776
  • MMIO devirt 0xFED00000 -> 4275044352
  • MMIO devirt 0xFEE00000 -> 4276092928
  • MMIO devirt 0xFF000000 -> 4278190080

Làm xong xuôi hết thì trong cái file cấu hình của bạn nó sẽ nhìn giống y chang như cái hình này nè: