Tất cả mọi người đều đang hiểu sai sự cố iPhone thành cục gạch vì năm 1970

    Lê Hoàng,  

    Lập luận của các trang tin công nghệ rằng việc cài đặt thời gian vào ngày 1/1/1970 khiến cho các giá trị ngày tháng trở về mức âm (dưới 0) và gây lỗi treo cứng là khá bất hợp lý.

    Sự cố cài đặt thời gian iPhone/iPad về ngày 1/1/1970 khiến cho các thiết bị này thành "cục gạch" đã liên tục xuất hiện trên các trang báo trong những ngày vừa qua. Lý do khiến cho lỗi ngớ ngẩn này lan rộng tới vậy là bởi người dùng các mạng xã hội đã liên tục chia sẻ một bức ảnh troll từ diễn đàn 4chan với thông tin sai lệch rằng làm như vậy sẽ giúp tạo ra bộ theme "hoài cổ" trên iPhone của họ.

    Sau khi sự cố xảy ra trên diện rộng, Apple đã lên tiếng xác nhận vấn đề và đưa ra khuyến cáo trên trang chủ rằng người dùng không nên cài đặt smartphone của họ về thời điểm trước tháng 5/1970. Tuy vậy, khi được trang tin công nghệ Gizmodo yêu cầu bình luận về sự kiện này, Apple đã thẳng thừng từ chối.

    Nói cách khác, công ty của Tim Cook sẽ không bao giờ làm rõ nguyên nhân gây ra hiện tượng iPhone bị brick khi chỉnh ngày giờ về thời điểm đầu năm 1970. Trong bài viết dưới đây, chúng tôi sẽ cùng bạn đọc tìm hiểu về cơ chế lưu thời gian trên iOS cũng như xác định nguyên nhân gây lỗi. Chúng tôi cũng sẽ điểm qua cách giải thích của blogger Tom Scott đang được nhiều trang công nghệ thế giới đăng tải lại trong những ngày qua.

    Bài viết dưới đây được thực hiện từ góc nhìn của một kỹ sư phần mềm với sự trợ giúp của anh Giang Phạm, senior developer của công ty Aviva, Singapore và anh Linh Nguyễn, senior developer tại Zalora Singapore.

    Mốc thời gian 1/1/1970: "Điểm khởi đầu của Unix"

    Trước hết, bạn hiểu rõ vai trò đặc biệt của mốc thời gian 0 giờ 0 phút 0 giây ngày 1/1/1970: tên tiếng Anh của mốc thời gian này là "Unix Epoch", trong đó "Epoch" có nghĩa là "điểm khởi đầu". "Unix" là tên của một nhóm hệ điều hành mang cùng một triết lý với hệ điều hành Unix do AT&T sáng tạo vào thập niên 1970.

     Bạn có biết đây chính là ông tổ của cả iOS, Android, Mac OS X lẫn gia đình Linux?

    Bạn có biết đây chính là "ông tổ" của cả iOS, Android, Mac OS X lẫn gia đình Linux?

    Triết lý chung của gia đình hệ điều hành Unix là thiết kế chia hệ điều hành thành nhiều module nhỏ có nhiệm vụ riêng biệt. Bạn có thể thấy bất ngờ khi biết điều này, nhưng Android thực chất lại là "họ hàng xa" của iOS trong gia đình Unix. "Gia đình" này được chia làm 2 nhánh chính: nhánh thứ nhất phát triển từ phiên bản Unix BSD của Đại học Berkeley, và sau này bao gồm NextSTEP (hệ điều hành của NeXT Inc, công ty do Steve Jobs sáng lập sau khi bị "đá" khỏi Apple), Mac OS X (xây dựng trên nền tảng NextSTEP) và iOS (xây dựng trên nền tảng Mac OS X). Nhánh còn lại phát triển từ dự án GNU, bao gồm: GNU phiên bản chính (một dự án trì trệ vì không ai có thể tạo ra kernel), GNU/Linux (hay gọi chung là "Linux", tạo thành khi Linus Torvald đem ghép bộ kernel Linux vào các module sẵn có của GNU) và quen thuộc nhất là Android. Ai cũng biết rằng hệ điều hành di động số 1 thế giới là một phiên bản đặc biệt của Linux, và bởi vậy thuộc về gia đình Unix.

    Các hệ điều hành của "gia đình" Unix đã phân hóa rất rõ rệt, đến mức mà bạn thậm chí còn chẳng thể chạy phần mềm iOS trực tiếp trên Mac OS X (native). Tuy vậy, chúng vẫn giữ lại một số đặc điểm chung, trong đó bao gồm cả mốc thời gian "Unix Epoch".

    Sở dĩ gọi Unix Epoch là điểm khởi đầu là bởi toàn bộ các mốc thời gian trong các hệ điều hành đều được tính bằng khoảng cách giữa mốc thời gian đó và thời điểm 0 giờ 0 phút 0 giây ngày 1/1/1970, theo đơn vị giây. Ví dụ, thời điểm 0 giờ 0 phút 0 giây ngày 1/1/2016 sẽ có giá trị là 1451606400 theo cách tính Unix, bởi khoảng cách từ Unix Epoch đến thời điểm trên là 1451606400 giây.

    Dĩ nhiên, theo cách tính này thì thời điểm 1/1/1970 sẽ có giá trị là 0. Và đây chính là điểm mấu chốt trong sự cố của iPhone.

    Hiện tượng "tràn số nguyên" (integer underflow/overflow)

     Tom Scott, một blogger nổi tiếng đã đưa ra lý do iPhone bị brick khi cài ngày về 1/1/1970.

    Tom Scott, một blogger nổi tiếng đã đưa ra lý do iPhone bị brick khi cài ngày về 1/1/1970.

    Trong một đoạn video được nhiều trang tin công nghệ đăng tải lại, một lập trình viên/blogger công nghệ khá nổi tiếng có tên Tom Scott đã lý giải về hiện tượng lỗi nói trên. Một trong những khái niệm được Scott nói tới là hiện tượng integer underflow, tạm hiểu tràn số nguyên phía dưới.

    Vậy thì thế nào là underflow? Hãy nhớ rằng các giá trị trên máy tính đều được lưu thành các bit 0 và 1. Do thời gian trên các hệ điều hành Unix được tính bằng đơn vị nhỏ nhất là giây, các mốc thời gian cũng sẽ được lưu bằng kiểu biến Integer (số nguyên). Lỗi xảy ra trên các mẫu iPhone/iPad có chip 64-bit, và 64 bit có thể lưu giá trị số nguyên tối đa là 2^64-1 và giá trị nhỏ nhất là 0. Trong đoạn video này, Scott cho rằng Apple không cho phép sử dụng giá trị âm khi lưu thời điểm ngày tháng trong iOS, tức là sử dụng dạng số nguyên không dấu – "unsigned integer" (chúng ta sẽ nói thêm về điều này ở phía dưới).

    Nếu như thời điểm hiện tại là 0 thì thời điểm 1 giây trước sẽ là -1, 2 giây trước là -2 v…v…. Một tin nhắn được gửi từ 1 phút trước sẽ được lưu giá trị thời gian là -60. Các con số âm này, theo phỏng đoán của Scott, sẽ không được iOS chấp nhận. Do các bit sẽ "quay vòng" khi bị gán giá trị nằm ngoài khoảng chấp nhận, giá trị nằm dưới khả năng thể hiện của hệ điều hành sẽ bị đẩy ngược lên giá trị cao nhất. Ví dụ, giá trị -1 sẽ bị đẩy về 2^64-1 (max), giá trị -2 sẽ bị đẩy về 2^64-2, -3 bị đẩy về 2^64-3. Hiện tượng này, như Scott giải thích, sẽ gây ra "những biểu hiện không được chú thích rõ" làm treo hệ thống. Blogger này cũng khẳng định Apple lẽ ra đã nên cho phép tùy chỉnh hệ thống về mức thời gian âm (tức trước thời điểm ngày 1/1/1970) để tránh gây lỗi.

     Tràn số trên iOS, theo Tom Scott.

    Tràn số trên iOS, theo Tom Scott.

    Tương tự như vậy, khi giá trị thời gian vượt quá mức tối đa là 2^64-1, ví dụ như là 2^64 hoặc 2^64 1 chẳng hạn, thì giá trị này sẽ bị đẩy về mức tối thiểu. Ví dụ, 2^64 sẽ bị đẩy về 0, 2^64 1 sẽ bị đẩy về 1… Hiện tượng này là "integer overflow", tạm hiểu là tràn số nguyên phía trên. Cả overflow và underflow đều xảy ra khi số bit được cung cấp là không đủ để thể hiện giá trị được gán.

    Bạn có thể bắt gặp hiện tượng tràn số trong cuộc sống thực. Ví dụ về hiện tượng overflow trên hệ thập phân có thể kể đến đồng hồ đếm cây số trên xe máy/ô tô: khi vượt qua mốc 99.999,9 cây số thì đồng hồ này sẽ quay ngược về 00000. Hoặc, hộp số xe máy thông thường (từ số "mo" đến số 4) có thể coi là ví dụ về hiện tượng tràn số trên hệ cơ số 5 (được thể hiện bằng 1 bit duy nhất): khi bạn cố gắng gạt số từ số 4 lên giá trị tiếp theo thì động cơ sẽ lại "về mo".

    "Year 2038": Thảm họa tầm cỡ Y2K

    Cũng chính về hiện tượng tràn số mà thế giới điện toán đang lo sợ về thời điểm 3 giờ 14 phút 07 giây ngày 19/1/2038. Đây là con số ngày tháng tối đa mà các hệ máy 32-bit có thể biểu hiện được: 2^31-1, tương đương với 2.147.483.647 giây sau thời điểm 1/1/1970. Đến thời điểm 3 giờ 14 phút 08 giây của ngày này, mốc thời gian trên các hệ máy sẽ bị đẩy về ngày 13/12/1901, gây rối loạn cho các hệ thống tương tự như những gì Y2K (có thể) đã gây ra.

     Mô phòng hiện tượng Y2038 do tràn số.

    Mô phòng hiện tượng Y2038 do tràn số.

    Đến đây, bạn có thể đặt ra câu hỏi: tại sao lại là 2^31-1 mà không phải là 2^32-1, và tại sao lại đẩy về mốc 13/12/1901 mà không phải là thời điểm 1/1/1970.

    Lý do là bởi trái với phân tích của Tom Scott, thời gian của các hệ điều hành Unix được biểu hiện dưới dạng số nguyên có dấu (signed integer). Ban đầu, các nhà thiết kế của Unix đã từng tranh cãi khá kịch liệt về việc chọn định dạng số nguyên có dấu hay số nguyên không dấu để biểu hiện thời gian Unix, nhưng cuối cùng vẫn lựa chọn số nguyên có dấu. Cho dù người dùng không nên chỉnh thời gian hiện tại của iPhone (hay smartphone Android, PC Ubuntu…) về thời điểm quá khứ, chắc chắn các lập trình viên vẫn sẽ có lúc cần thể hiện một mốc thời gian trước 1970.

    Trên kiểu biến số nguyên có dấu, bit đầu tiên bên trái sẽ được dùng để xác định dấu âm/dương của biến: 1 là âm, 0 là dương.

    Trên kiến trúc 32-bit, bit đầu tiên dùng để thể hiện giá trị dương, 31 bit còn lại sẽ được sử dụng để thể hiện giá trị. Do đó, với kiểu signed integer, giá trị tối đa sẽ là 2^31-1 = 2.147.483.647, tương đương với thời điểm ngày 19/1/2038, và giá trị thời gian sớm nhất có thể thể hiện được sẽ là -2^31, tức là vào ngày 13/12/1901.

     Bạn không thể chỉnh thời gian quá mốc này trên iOS.

    Bạn không thể chỉnh thời gian quá mốc này trên iOS.

    Trở lại với Apple: trên bất kỳ một chiếc iPhone hoặc iPad nào, bao gồm cả những chiếc iPhone/iPad chạy chip 32-bit lẫn 64-bit, giá trị ngày tháng tối đa mà bạn có thể cuộn tới là 3 giờ 0 phút ngày 2/1/2038. Khi cố gắng cuộn khung thời gian của iPhone/iPad qua thời điểm này, thanh cuộn sẽ tự động nhảy về vị trí trên. Như vậy, rõ ràng là bất kể trên thiết bị iOS 32-bit hay 64-bit thì Apple cũng đang lựa chọn giới hạn thời gian là mức 32-bit của kiểu biến số nguyên có dấu.

     Tràn số trên iOS

    Tràn số trên iOS

    Chính vì điều này, chúng tôi cho rằng dù ứng dụng Settings của Apple có không cho phép bạn lựa chọn thời gian hiển thị ở mức âm (trước 1/1/1970) thì hệ điều hành cũng sẽ không gặp lỗi khi các giá trị thời gian dưới 0 xuất hiện. Chưa kể, để gặp lỗi tràn dưới ở mốc 13/12/1901 thì một dữ liệu ngày tháng nào đó trên hệ điều hành sẽ phải có giá trị cách thời điểm hiện tại là 69 năm – một con số hoàn toàn bất khả thi vì các loại metadata có thể tương tác cùng Mac OS X/iOS khó có thể có tuổi đời lên tới mức 30 năm chứ đừng nói là 69 năm.

    Quá trình port iOS từ 32-bit lên 64-bit có thể là nguyên nhân gây ra lỗi

    Đoạn video của Tom Scott cũng đã bỏ qua một dữ liệu quan trọng: tình trạng lỗi chỉ xảy ra trên các dòng iPhone/iPad/iPod Touch có chip 64-bit, tức là từ iPhone 5s/iPad mini 2 trở lên. Các thông tin của 9to5Mac cho biết iPhone 5 (có chip 32-bit) không gặp phải lỗi này.

    Như đã phân tích ở trên giá trị thời gian trên cả thiết bị iOS 64-bit lẫn thiết bị iOS 32-bit đều có mốc giới hạn là năm 2038, tất cả các thiết bị iOS vẫn đang tuân theo mức giới hạn của kiểu số nguyên có dấu 32-bit (theo chúng tôi, là để tránh phân mảnh giữa các hệ máy của Apple, gây khó khăn cho cộng đồng phát triển ứng dụng). Bởi vậy, xét tới tình trạng lỗi chỉ xảy ra trên máy 64-bit, việc chuyển đổi các biến số hệ thống từ mã nguồn 32-bit lên mã nguồn 64-bit chắc chắn đã gặp vấn đề.

     Chỉ duy nhất iPhone/iPad có chip 64-bit là gặp hiện tượng brick 1970.

    Chỉ duy nhất iPhone/iPad có chip 64-bit là gặp hiện tượng brick 1970.

    Một kịch bản mà chúng tôi có thể nghĩ tới là một kỹ sư… ngớ ngẩn nào đó đã viết mã nguồn dịch chuyển biến 32-bit thành 64-bit bằng cách thêm 32 bit 0 vào đằng trước. Tất cả những số âm (bắt đầu bằng bit 1) sẽ biến thành số dương (bắt đầu bằng bit 0). Trong trường hợp này, một thời điểm trong quá khứ sẽ vọt lên trở thành một thời điểm trong tương lai. Giả sử iPhone cần tra cứu một giá trị nào đó vào thời điểm x phút trước, lỗi biến số âm thành số dương sẽ khiến chiếc điện thoại của bạn tìm đến một giá trị được lưu ở thời điểm y phút trong tương lai – một giá trị rõ ràng là không hề tồn tại. Việc tham chiếu tới các biến số không tồn tại hiển nhiên là sẽ khiến cho hệ thống treo cứng.

    Đứng từ góc nhìn của một người có hiểu biết về toán học, một lỗi như vậy sẽ thực sự là ngớ ngẩn. Song, việc chuyển đổi biến 32-bit thành biến 64-bit chắc hẳn đã được thực thi trên một tầng ngôn ngữ cấp thấp (xa ngôn ngữ người, gần ngôn ngữ máy) nên việc sai sót hoàn toàn có thể xảy ra. Trước khi trò đùa biến iPhone thành cục gạch này trở nên phổ biến, gần như không có ai chỉnh thời gian về mức < 0 trên iPhone, do đó việc lỗi này lọt qua bộ máy kiểm thử của Apple cũng là không quá khó tưởng tượng.

    Điều gì sẽ xảy ra nếu như Apple thực sự đặt chặn dưới ở mốc 1970

    Cần phải nhắc lại rằng chúng tôi không thực sự tin vào khả năng Apple dùng mốc 1970 làm thời gian tối thiểu, vì đây là vấn đề đã ngã ngũ trong cộng đồng tác giả Unix. Nhưng, trong trường hợp vì một lý do nào đó mà Apple lại chọn giới hạn của 31-bit số nguyên không dấu (một khả năng rất, rất vô lý), điều gì sẽ xảy ra?

    Tom Scott gọi tình huống này là "các tình huống không được chú thích rõ" ("undocumented behavior", cũng có thể được hiểu là những tình huống mà tác giả phần mềm ban đầu không hình dung ra), nhưng thực chất điều sẽ xảy ra cũng sẽ là biến giá trị âm bị biến trở thành giá trị dương, nhưng con số mới sẽ gần với mốc 2^31-1 hơn là các giá trị khi bị thêm bit. Mốc 2^64 mà Tom Scott đưa ra, như đã phân tích ở các phần trước, cũng là không hợp lý.

     Dennis Ritchie, cha đẻ của Unix, đã có lần khẳng định rằng ông muốn cả ngày sinh lẫn ngày mất của mình có thể tồn tại trên khoảng thể hiện của Unix. Vì vậy, hệ điều hành này đã lựa chọn số nguyên có dấu để lưu thời gian.

    Dennis Ritchie, cha đẻ của Unix, đã có lần khẳng định rằng ông muốn cả ngày sinh lẫn ngày mất của mình có thể tồn tại trên khoảng thể hiện của Unix. Vì vậy, hệ điều hành này đã lựa chọn số nguyên có dấu để lưu thời gian.

    Vẫn còn nhiều ẩn số

    Khi thực hiện bài viết này, chúng tôi cũng nhận thấy một số điểm bất hợp lý trong cách thực thi của Apple. Đầu tiên, khuyến cáo chính thức của công ty này cho biết bạn không nên chỉnh thời gian về thời điểm từ tháng 5 năm 1970 trở về trước, chứ không phải là mốc 1/1/1970. Mốc thời gian xa nhất trong tương lai có thể lựa chọn cũng là ngày 2/1/2038 chứ không phải là ngày 19/1/2038 như trong định nghĩa của Y2038. Như vậy, khung thời gian giới hạn mà Apple đặt ra là hạn hẹp hơn khung giới hạn thời gian thông thường của Unix. Vì sao Apple làm vậy, chỉ Apple mới biết.

    Thêm nữa, chưa có một hệ điều hành Unix nào gặp phải lỗi tương tự. Các thông báo lỗi về tình trạng brick máy/treo máy khi chỉnh ngày tháng về trước 1/1/1970 trên Android và Linux không hề tồn tại (trên Google), dù rằng đôi khi các hệ thống Linux hoặc thậm chí là máy Android thời kỳ đầu có thể gặp lỗi bỗng dưng nhảy ngày về 1/1/1970.

    Đáng tiếc rằng các đoạn mã nguồn xử lý ngày tháng nằm ở các tầng rất sâu của hệ điều hành, và trừ khi bạn là một kỹ sư trực tiếp phụ trách các tầng này, bạn sẽ không thể biết được nguyên nhân vì sao lỗi brick iPhone khi kéo thời gian trở về ngày 1/1/1970. Ngay cả các kỹ sư thành thục các ngôn ngữ của Táo như Swift hay Objective-C cũng không thể nắm được câu trả lời, bởi phần xử lý đó được "đóng gói" ngoài những gì họ nắm biết.

    Nói tóm lại, tất cả những gì chúng tôi đưa ra phía trên cũng chỉ là những phân tích hợp lý nhất trong phạm vi hiểu biết của chúng tôi về kiểu biến số nguyên và cách tính thời gian trên iOS. Chúng tôi không thể khẳng định với các bạn rằng những phân tích này là 100% chính xác, bởi vậy, nếu bạn đọc có một cách hiểu khác về sự cố hy hữu của iPhone, hãy cùng chia sẻ nhé!

     

     

    Tin cùng chuyên mục
    Xem theo ngày