`fcntl64`の代わりに古いlibc` fcntl`へのリンクを強制する方法は?


8

そうです(2018年8月発売)GLIBC 2.28は fcntlのにかなり積極的な変更を行いました。定義が変更され<fcntl.h>、外部関数ではなくなりました。なく、#definefcntl64

その結果、このglibcを使用するシステムでコードをコンパイルした場合、つまりfcntl()使用した場合、結果のバイナリは2018年8月より前のシステムでは実行されません。これは、さまざまなアプリケーションに影響します。 .fcntl()のマニュアルページは、サブ関数の小さなユニバースのエントリポイントであることを示しています。

https://linux.die.net/man/2/fcntl

必要なGLIBC関数の特定のバージョンをリンカーに伝えることができれば、すばらしいでしょう。しかし、私が見つけた最も近いものは、別の投稿への回答で説明されているこのトリックでした:

「.soファイル内の古いシンボルバージョンに対するリンク」への回答

これはもう少し複雑です。 va_listを取るa fcntlなしの可変vffcntl個です。このような状況では、可変個関数の呼び出しを転送できません。:-(

意図的に低い依存関係を持つ安定したコードがある場合、それを現在のUbuntuでビルドするのは失望です...そして、実行可能ファイルを1年前にリリースされた別のUbuntuで実行することを拒否します(ほぼその日まで)。これにはどのような手段がありますか?

回答:


6

これにはどのような手段がありますか?

GLIBCが#define USE_FCNTL_NOT_FCNTL64多くのことを言う方法がなかったという事実。正しいか間違っているかにかかわらず、ほとんどのOS +ツールチェーンメーカーは、システムの古いバージョンのバイナリを新しいバージョンからターゲットにすることは、高い優先度ではないと決定しているようです。

最も抵抗の少ない方法は、プロジェクトを構築する最も古いOS +ツールチェーンの周りに仮想マシンを置くことです。バイナリが古いシステムで実行されると思われる場合はいつでも、それを使用してバイナリを作成してください。

だが...

  • 使用がオフセットサイズの変更の影響を受けないfcntl()呼び出しのサブセットにあると確信している場合(つまり、バイト範囲ロックを使用していない)
  • または、後方互換性のある構造体定義を使用するために、オフセットケースについてコードを精査します
  • そして、ブードゥー教を怖がっていません

...その後、読み続けます。

名前は異なり、fcntlはva_listをとるvffcntlを持たない可変長です。このような状況では、可変個関数の呼び出しを転送できません。

...次に、前述のラッピングトリックを適用するには、 fcntl()のインターフェースドキュメントを1行ずつ調べ、バリアントをそのように解凍し、新しいバリアード呼び出しでラップバージョンを呼び出す必要があります。

幸い、それほど難しいことではありません(fcntlは、文書化された型で0または1の引数を取ります)。他の誰かをトラブルを救うために、そのためのコードを以下に示します。必ず--wrap = fcntl64をリンカーに渡してください(ldを直接呼び出さない場合は-Wl-wrap = fcntl64):

asm (".symver fcntl64, fcntl@GLIBC_2.2.5");

extern "C" int __wrap_fcntl64(int fd, int cmd, ...)
{
    int result;
    va_list va;
    va_start(va, cmd);

    switch (cmd) {
      //
      // File descriptor flags
      //
      case F_GETFD: goto takes_void;
      case F_SETFD: goto takes_int;

      // File status flags
      //
      case F_GETFL: goto takes_void;
      case F_SETFL: goto takes_int;

      // File byte range locking, not held across fork() or clone()
      //
      case F_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // File byte range locking, held across fork()/clone() -- Not POSIX
      //
      case F_OFD_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // Managing I/O availability signals
      //
      case F_GETOWN: goto takes_void;
      case F_SETOWN: goto takes_int;
      case F_GETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_SETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_GETSIG: goto takes_void;
      case F_SETSIG: goto takes_int;

      // Notified when process tries to open or truncate file (Linux 2.4+)
      //
      case F_SETLEASE: goto takes_int;
      case F_GETLEASE: goto takes_void;

      // File and directory change notification
      //
      case F_NOTIFY: goto takes_int;

      // Changing pipe capacity (Linux 2.6.35+)
      //
      case F_SETPIPE_SZ: goto takes_int;
      case F_GETPIPE_SZ: goto takes_void;

      // File sealing (Linux 3.17+)
      //
      case F_ADD_SEALS: goto takes_int;
      case F_GET_SEALS: goto takes_void;

      // File read/write hints (Linux 4.13+)
      //
      case F_GET_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_RW_HINT: goto takes_uint64_t_ptr;
      case F_GET_FILE_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_FILE_RW_HINT: goto takes_uint64_t_ptr;

      default:
        fprintf(stderr, "fcntl64 workaround got unknown F_XXX constant")
    }

  takes_void:
    va_end(va);
    return fcntl64(fd, cmd);

  takes_int:
    result = fcntl64(fd, cmd, va_arg(va, int));
    va_end(va);
    return result;

  takes_flock_ptr_INCOMPATIBLE:
    //
    // !!! This is the breaking case: the size of the flock
    // structure changed to accommodate larger files.  If you
    // need this, you'll have to define a compatibility struct
    // with the older glibc and make your own entry point using it,
    // then call fcntl64() with it directly (bear in mind that has
    // been remapped to the old fcntl())
    // 
    fprintf(stderr, "fcntl64 hack can't use glibc flock directly");
    exit(1);

  takes_f_owner_ex_ptr:
    result = fcntl64(fd, cmd, va_arg(va, struct f_owner_ex*));
    va_end(va);
    return result;

  takes_uint64_t_ptr:
    result = fcntl64(fd, cmd, va_arg(va, uint64_t*));
    va_end(va);
    return result;
}

実際にビルドしているバージョンによっては、使用できない場合は、#ifdefフラグセクションの一部を削除する必要がある場合があります。

これは非常にさまざまなアプリケーションに影響します... fcntl()のマニュアルページは、それがサブ関数の小さなユニバースのエントリポイントであることを示しています

...そしてそれはおそらく人々への教訓になるはずです:可変的虐待を通じてそのような「キッチンシンク」関数を作成することは避けてください。


この投稿の動機について議論したい人のために... 今日の世界でのバイナリ転送
は次のとおり

そして、これは何を壊すでしょうか?の使用をやめることでfcntl64()、2 GBを超えるファイルにアクセスするときにバグが発生することはありますか?知る唯一の方法は、すべてのfcntl()用途の完全な回帰テストを行うことです。 プロジェクトをビルドする最も古いOS +ツールチェーンの周りの仮想マシンを保つ 本当の答えであるとIMOフロントアップする必要があります。
Andrew Henle


1
「システムの古いバージョンのバイナリを新しいバージョンからターゲットにすることは、優先度が高くありません。」-「優先度が高くありません」ではなく、明示的な非目標です。
ロシア語を採用

1
@AndrewHenle 「このライブラリ/このOSで実行されないものは、今後何も出現しません」?誰もその約束をすることはできず、それに頼ることはできません。」 =>これは明らかに誤りです。つまり、古いバージョンのOS /ツールチェーンを結果に使用できる場合、新しいバージョンのツールチェーンを使用できるという意味です。その結果を取得するためのスイッチもあるかもしれません。少し前までは、システムで実行されるバイナリをビルドできないコンパイラのリリースが、前日に「最新」と見なされることは、容認できないと考えられていました。その船はここで多くの人のために出航したようです
HostileForkはSEを信頼してはいけないと言っていますSE

2

fcntl代わりに古いlibcへのリンケージを強制する方法はfcntl64

の古いバージョンに対してコンパイルする libcます。限目。

glibcは上位互換性がないため、下位互換性のみがあります。

GNU Cライブラリは、下位互換性、移植性、および高性能ISO Cライブラリになるように設計されています。ISO C11、POSIX.1-2008、およびIEEE 754-2008を含むすべての関連規格に従うことを目的としています。

上位互換性が保証されていなければ、他に何が適切に機能しないのかわかりません


これを追加するには、可能であれば通常のパッケージビルドプロセスを使用します。通常、ほとんどのディストリビューションでパッケージをビルドする手順には、そのディストリビューションで使用されるライブラリのバージョンのインストールが含まれます。そのため、ビルドプロセスでテストを実行すると、問題を早期に発見できます
XANi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.