製品に搭載する組込みソフトウェアなどを開発していると、
そろそろ「2038年問題」を意識し始めている方も多いのではないでしょうか?
本記事では、2038年問題とは何か、どういった対策方法があるのかを中心に解説します。
2038年問題とは?
2038年1月19日 3時14分7秒(UTC)を過ぎると、コンピュータが誤動作する可能性がある問題です。
誤動作するメカニズム
コンピュータの内部で動作するソフトウェアは、○年△月□日 ●時▲分■秒のような
「実時間(RTC:Real-Time clock)」を扱います。
ファイルの作成日時や更新日時、時計表示など目に触れることも多いかと思います。
この実時間を1つの整数で表現しているコンピュータ、およびコンピュータプログラムがあり、
「1970年1月1日 0時0分0秒(UTC)」からの経過秒数を採用しているシステムがあります。
前述の「経過時間」は、ソフトウェア内部では「time_t型」で表現されています。
伝統的な実装で time_t は int(符号付き32bit)または long(64bit OSでは 64bit、32bit なら 32bit)です。
つまり、32bit だと正数の最大値は 2,147,483,647 となり、約68年のカウントまでしかできません。
そのため、1970年に加算して2038年に「経過秒数」がオーバーフローし、
負の値となることで誤動作する可能性があるということです。
問題となる条件
以下の「AND」で2038年問題が発生する可能性の条件が成立します。
- 実時間(RTC)を「1970年1月1日 0時0分0秒(UTC)」からの経過秒数で管理
- 経過秒数を符号付き32bitで保持、またはアクセスする
どのような誤動作が考えられる?
実時間(時刻)を使ってどのような処理を行っているかによるため、
一概にコレだ!とは言えませんが、プログラム視点では以下のような問題が生じます。
- 異常値として扱われる
負の値となることで、正常値と見なされなくなる可能性があります。 - 論理が反転する(=時系列が崩れる、など)
大小比較などを行っている場合、論理が反転することになります。 - 誤った演算結果となる(=経過時間、うるう年、など)
演算を行っている箇所では、正負反転で誤った結果を得ることになります。
実際はこれらの複合問題もあり、表面上に出てくることになるでしょう。
よくある誤解
「2038年問題を対策したOSを搭載すれば問題ない」と考えている方も多いのではないでしょうか?
例えば、「64bit Linux を搭載してればOK」みたいな感じです。
これは、大きな間違いです!
この後、順に解説していきます。
実時間(RTC)の所在と利用者
まず、RTCを情報として保持している場所、及び利用していそうなソフトウェアの図を見てみましょう。
※Windowsはブラックボックスのため想定で描いていますが、機能としては存在しているはずです。

Hardware層
- RTC Module
実時間をカウントする専用のモジュールです。
Chipset群に含まれる場合もありますが、当然「RTC」の情報を保持しています。 - CMOS
BIOS や UEFI が搭載されている場合にセットで搭載されます。
BIOS や UEFI で設定した実時間を、ここに「RTC」の情報として保存されます。 - FlashROM
BIOS/UEFI や、Intel TXE(Trusted Execusion Engine)のファームウェアが格納されています。
CPUで動作させるプログラムの格納先とは別のデバイスです。
最近のファームは、ファイルシステムを使用しているため、ココにも「RTC」の情報があります。 - HDD/SSD
BOOTプログラム、OS、アプリケーション、など、大半のソフトウェアやデータが格納されています。
こちらもファイルシステムを利用しているため、「RTC」の情報があります。
メモ
ファイルシステムでは、ファイルの「作成日」「更新日」に利用している他、
パーティションを管理する情報(Super Block)にも利用しています。
Software層
- OS
デバイスから取得したRTC情報や、上位から設定されたRTC情報をメモリ上に保存しています。
これを参照して、プロトコルスタックやファイルシステムを扱うドライバを経由し、
外部機器と通信したり、データをSSD/HDDに書き出したりしています。 - LibraryまたはRuntime/Framework
システムコールやサービスコールをラップし、上位アプリが利用しやすいAPIを提供しています。
Linuxでは glibc, pthread, curl などが RTC 情報へアクセスします。
gettimeofday, ftime, ctime, pthread_cond_timedwait, strftime などです。
Windows では、アプリケーションを実行する際に必要となる Runtime/Framework が該当します。 - Daemon/Service
TCP/IPの上位アプリケーション層として NTP, RTP, RTCP などが実時間を扱うプロトコルです。
それらのサービスやデーモンが RTC 情報へアクセスします。 - Application
メーカーが独自に実装する中心となる部分。この図では、ミドルウェアもこの層に該当します。
機器の仕様や実現していることに大きく依存しますが、ログや経過時間の計算などで
少なくとも RTC 情報を参照しているでしょう。
「問題ない」と言えない感じがしてきましたね。
実時間(RTC)にアクセスする経路
アクセスする経路を見極めることが、対策を考える際に重要となります。
上図の通り、RTCの情報はハードウェアまたはハードウェアに近い層に保存されています。
そのため、NTPで取得した時刻の設定時や、ファイル操作行う際、間の層を必ず通過することになります。
また、ライブラリやランタイム自身が独自にアクセスする場合や、
OS や ハードウェア自身も独自にアクセスします。
つまり、実時間(RTC)にアクセスする経路は無数に存在するということです。
上記より、64bit OS を搭載していたとしても int は 32bit 固定ですし、32bit アクセスも可能です。
例えば、以下のケースが経路中に存在すると、問題となる場合があります。
- 64bit OS 上で 32bit のプログラムを実行(Multi-Arch機能)
- 古い Runtime を使用している
- 32bit 時代の OSS を TTIME64 というような configure を無効にしてビルド
- INT_MAX や (1<<31) などを使って値域チェックを行っている
- time_t を int で使用、またはキャストしている
対策案を考える【UNIX/Linux編】
対策案はいくつかありますが、影響範囲を考えると選択肢は限られます。
影響や実現性に関して、個別にみていきます。
64bit OS/ライブラリ を搭載
おそらく、このページにたどり着いた方は 32bit マイコンを使用していると想定しています。
64bit OS は搭載できませんので、本案は飛ばします。
過去の時刻をRTCに設定(オーバーフローさせない)
これが一番現実的な解になる方が多いと思います。
ただし、ソフトウェア層で RTC 情報の IN/OUT を全ておさえ、「完全な過去の世界」を作れることが条件。
コード量的に、独自に開発したソフトウェアの方が圧倒的に少ないと予想されるため、
オーバーフローさせないようにすることが一番影響範囲を抑えることができます。
具体的には、NTP や CMOS からの RTC 情報を誤魔化し、内部は過去の時間で動作させます。
外部との通信等で時刻を出力する場合は、そのプロトコルに合った時刻に補正します。
また、対策事例もいくつか報告されているため、本案が最も実現性高そうです。
- https://www.ipsj.or.jp/dp/contents/publication/39/S1003-1809.html
- https://sel.ist.osaka-u.ac.jp/lab-db/betuzuri/archive/1149/1149.pdf
オーバーフローしたら2038年以降の時刻に読替え
NTP と同じ考え方の案です。
しかし、この案の採用には先ず、オーバーフローしても大丈夫なことの確認が必要になります。
本末転倒ですので、飛ばします。
time_t 型を 64bit 化
time_t の宣言は、すべてのソフトウェアが参照する Kernel のヘッダーで定義されています。
全てのソフトウェアを再ビルドし、再試験し・・・という影響範囲が膨大となります。
また、そのあとに 32bit アクセスしている箇所がないかを点検する必要があり実現性が低いでしょう。
基準時刻「1970年」を変更
こちらも Kernel の変更が必要となります。
ヘッダーというレベルではなく、ロジックの変更を伴います。
また、1970年を基準と意識したロジックを全て見つけ出し、その場所の修正も伴います。
影響範囲がソフトウェア全体となるため、現実的ではないでしょう。
Linux Kernel v5.4 以降に更新
32bit Linux で、v5.4 以降であれば本問題の対策が施されています。
先の「過去時刻を設定」が実現できない場合、次の有力候補となります。
glibc の更新も伴うため、ライブラリ群は軒並み更新が必要となります。
また、開発したアプリケーションも見直しが必要になるでしょう。
影響範囲は広いですが、無数の点検を行うより圧倒的にゴールできる可能性が高い案です。
参考
こちら「2038年問題への対応」で紹介されています。
Linux に導入された変更をマージ
おそらく v4 系以前の Linux を使っているのではないでしょうか。
だとすると、メジャーバージョンが異なるため、マージはほぼ不可能でしょう。
また、数千件の変更が毎月実施される Linux Kernel の履歴から、
該当するコミットを全て見つけ出すことは至難の業です。
対策案を考える【Windows編】
Windows の場合、99% 以上がブラックボックスであり、ソースコードが隠蔽されています。
点検するすべがありません。
しかし、Wikipedia では以下のような記載があり、よほど古いOSやRuntimeを使用していない限り
問題とはならず、問題ある場合はOS/Runtimeの更新しか手がないようです。
32bit版のWindows (Win32) では内部時刻の表現に64bit化された構造体を使っており、旧OSに関する一部のケースを除き、32ビット版であっても2038年問題は発生しない。
ただし、Microsoft C/C++ (MS-C) および古いバージョンのMicrosoft Visual C++ (MSVC) においては、32bitを使って定義されていたため、これらの古い処理系のC/C++ランタイムライブラリ (CRT) を利用して構築されたアプリケーションやDLLなどは、2038年問題を抱えていることになる。
x64アーキテクチャの64bit版Windows OS上では32bitアプリケーションも動作させることができるが、古い32bitアプリケーションにおける2038年問題は、たとえWindows OSを64bit版に入れ替えたとしても回避することはできない。
おわりに
ソフトウェアは「果物」です。
保守を怠れば腐っていき、資産価値は「ゼロ」になります。
リファクタリング等、定期的にアーキテクチャや実装を見直す習慣をつけましょう。