ফায়ারফক্স ডেভেলপমেন্ট অনেক ক্রস-প্ল্যাটফর্ম পার্থক্য এবং নির্ভরশীলতার সমন্বয়ের অনন্য বৈশিষ্ট্যগুলিকে উন্মোচন করে। Firefox-এ কাজ করা প্রকৌশলীরা নিয়মিত এই চ্যালেঞ্জগুলি কাটিয়ে উঠতে পারেন এবং যদিও আমরা সেগুলির সবকটি বিস্তারিত বলতে পারি না, আমরা মনে করি আপনি কিছু সম্পর্কে শুনে উপভোগ করবেন তাই সাম্প্রতিক প্রযুক্তিগত তদন্তের একটি নমুনা এখানে দেওয়া হল।
ফায়ারফক্স 120 বিটা চক্র চলাকালীন, উল্লেখযোগ্য ভলিউম সহ আমাদের রাডারগুলিতে একটি নতুন ক্র্যাশ স্বাক্ষর উপস্থিত হয়েছে.
সেই সময়ে, অপারেটিং সিস্টেম জুড়ে বিতরণ থেকে জানা যায় যে ক্র্যাশ ভলিউমের 50% এরও বেশি উবুন্টু 18.04 LTS ব্যবহারকারীদের কাছ থেকে আসে।
প্রধান প্রক্রিয়া একটি মধ্যে ক্র্যাশ CanvasRenderer
থ্রেড, নিম্নলিখিত কল স্ট্যাক সহ:
0 firefox std::locale::operator= 1 firefox std::ios_base::imbue 2 firefox std::basic_ios<char, std::char_traits<char> >::imbue 3 libxul.so sh::InitializeStream<std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> > > /build/firefox-ZwAdKm/firefox-120.0~b2+build1/gfx/angle/checkout/src/compiler/translator/Common.h:238 3 libxul.so sh::TCompiler::setResourceString /build/firefox-ZwAdKm/firefox-120.0~b2+build1/gfx/angle/checkout/src/compiler/translator/Compiler.cpp:1294 4 libxul.so sh::TCompiler::Init /build/firefox-ZwAdKm/firefox-120.0~b2+build1/gfx/angle/checkout/src/compiler/translator/Compiler.cpp:407 5 libxul.so sh::ConstructCompiler /build/firefox-ZwAdKm/firefox-120.0~b2+build1/gfx/angle/checkout/src/compiler/translator/ShaderLang.cpp:368 6 libxul.so mozilla::webgl::ShaderValidator::Create /build/firefox-ZwAdKm/firefox-120.0~b2+build1/dom/canvas/WebGLShaderValidator.cpp:215 6 libxul.so mozilla::WebGLContext::CreateShaderValidator const /build/firefox-ZwAdKm/firefox-120.0~b2+build1/dom/canvas/WebGLShaderValidator.cpp:196 7 libxul.so mozilla::WebGLShader::CompileShader /build/firefox-ZwAdKm/firefox-120.0~b2+build1/dom/canvas/WebGLShader.cpp:98
প্রথম নজরে, আমরা WebGL কে দোষ দিতে চাই। C++ স্ট্যান্ডার্ড লাইব্রেরি ফাংশন ভুল হতে পারে না, তাই না?
কিন্তু WebGL কোড দেখার সময়, নিচে সংক্ষিপ্ত C++ এর সম্পূর্ণ বৈধ লাইনে ক্র্যাশ ঘটে:
std::ostringstream stream; stream.imbue(std::locale::classic());
এই কোডটি কখনই ক্র্যাশ করা উচিত নয় এবং তবুও এটি করে। আসলে, স্ট্যাকটি ঘনিষ্ঠভাবে পর্যবেক্ষণ করা তদন্তের জন্য একটি প্রথম নেতৃত্ব দেয়:
যদিও আমরা C++ স্ট্যান্ডার্ড লাইব্রেরির অন্তর্গত ফাংশনগুলির সাথে ক্র্যাশ করি, এই ফাংশনগুলি ফায়ারফক্স বাইনারিতে বাস করে বলে মনে হয়।
এটি একটি অস্বাভাবিক পরিস্থিতি যা ফায়ারফক্সের অফিসিয়াল বিল্ডগুলির সাথে কখনই ঘটে না।
তবে বিতরণের জন্য কনফিগারেশন সেটিংস পরিবর্তন করা এবং একটি আপস্ট্রিম উত্সে ডাউনস্ট্রিম প্যাচ প্রয়োগ করা খুব সাধারণ, এটি নিয়ে কোনও উদ্বেগ নেই।
তাছাড়া, ফায়ারফক্স বিটার শুধুমাত্র একটি বিল্ড আছে যা এই ক্র্যাশের কারণ।
যেকোন ELF বাইনারির সাথে যুক্ত একটি অনন্য শনাক্তকারীর জন্য আমরা এটি জানি।
এখানে, যদি আমরা ফায়ারফক্স 120 বিটা (যেমন 120b9) এর কোনো নির্দিষ্ট সংস্করণ বেছে নিই, তাহলে ক্র্যাশগুলি ফায়ারফক্সের জন্য একই অনন্য শনাক্তকারী এম্বেড করে।
এখন, আমরা কিভাবে অনুমান করতে পারি কি বিল্ড এই অদ্ভুত বাইনারি উত্পাদন করে?
একটি দরকারী ব্যবহারকারীর মন্তব্য উল্লেখ করেছে যে আপডেট করার পর থেকে তারা নিয়মিত এই ক্র্যাশটি অনুভব করে৷ 120.0~b2+build1-0ubuntu0.18.04.1.
এবং এই বিল্ড শনাক্তকারী খুঁজছেন, আমরা দ্রুত পৌঁছান ফায়ারফক্স বিটা পিপিএ.
তারপর প্রকৃতপক্ষে, আমরা একটি উবুন্টু 18.04 LTS ভার্চুয়াল মেশিনে এটি ইনস্টল করার মাধ্যমে ক্র্যাশটি পুনরুত্পাদন করতে সক্ষম হয়েছি: এটি কোনও WebGL পৃষ্ঠা লোড করার সময় ঘটে!
হাতে এখন বাইনারি সঙ্গে, চলমান nm -D ./firefox
libstdc++ সম্পর্কিত বেশ কয়েকটি চিহ্নের উপস্থিতি নিশ্চিত করে যা পাঠ্য বিভাগে থাকে (T
চিহ্নিতকারী)।
libstdc++ থেকে টেমপ্লেটেড এবং ইনলাইন চিহ্ন সাধারণত দুর্বল হিসাবে প্রদর্শিত হয় (W
মার্কার), তাই এই পরিস্থিতির জন্য শুধুমাত্র একটি ব্যাখ্যা আছে: ফায়ারফক্স স্ট্যাটিকভাবে libstdc++ এর সাথে যুক্ত করা হয়েছে, সম্ভবত এর মাধ্যমে -static-libstdc++
.
সৌভাগ্যবশত, বিল্ড লগগুলি সমস্ত উবুন্টু প্যাকেজের জন্য উপলব্ধ।
কিছু খনন করার পরে, আমরা খুঁজে পাই 120b9 বিল্ডের লগযা প্রকৃতপক্ষে রেফারেন্স ধারণ করে -static-libstdc++
.
কিন্তু কেন?
আবার, সবকিছু ভালভাবে নথিভুক্ত করা হয়েছে, এবং ভালভাবে প্রশিক্ষিত খনন দক্ষতার জন্য আমরা পৌঁছাতে পেরেছি একটি বাগ রিপোর্ট যে আকর্ষণীয় অন্তর্দৃষ্টি প্রদান করে.
ফায়ারফক্সের জন্য একটি আধুনিক C++ কম্পাইলার প্রয়োজন, এবং তাই একটি আধুনিক libstdc++, যা উবুন্টু 18.04 LTS-এর মতো পুরানো সিস্টেমে অনুপলব্ধ।
বিল্ড ব্যবহার করে -static-libstdc++
এই ফাঁক বন্ধ করতে.
যদিও এটি অদ্ভুত সেটআপ ব্যাখ্যা করে।
ক্র্যাশ সম্পর্কে কি?
যেহেতু আমরা এখন এটি পুনরুত্পাদন করতে পারি, আমরা একটি ডিবাগারে ফায়ারফক্স চালু করতে পারি এবং আমাদের তদন্ত চালিয়ে যেতে পারি।
ক্র্যাশ সাইট পরিদর্শন করার সময়, আমরা ক্র্যাশ বলে মনে করি std::locale::classic()
সঠিকভাবে শুরু করা হয় না।
এর বাস্তবায়নে উঁকি দেওয়া যাক।
const locale& locale::classic() { _S_initialize(); return *(const locale*)c_locale; }
_S_initialize()
এটা নিশ্চিত করার দায়িত্বে আছে c_locale
আমরা এটির একটি রেফারেন্স ফেরত দেওয়ার আগে সঠিকভাবে শুরু করা হবে।
এই অর্জন করতে, _S_initialize()
অন্য ফাংশন কল করে, _S_initialize_once()
.
void locale::_S_initialize() { #ifdef __GTHREADS if (!__gnu_cxx::__is_single_threaded()) __gthread_once(&_S_once, _S_initialize_once); #endif if (__builtin_expect(!_S_classic, 0)) _S_initialize_once(); }
ইন _S_initialize()
আমরা প্রথম জন্য একটি wrapper মাধ্যমে যান pthread_once()
: এই কোডে পৌঁছানো প্রথম থ্রেডটি গ্রাস করে _S_once
এবং কল _S_initialize_once()
যেখানে অন্যান্য থ্রেড (যদি থাকে) অপেক্ষা করছে _S_initialize_once()
সম্পূর্ণ করতে
এই বরং ব্যর্থ প্রমাণ দেখায়, তাই না?
এমনকি একটি অতিরিক্ত সরাসরি কল আছে _S_initialize_once()
যদি _S_classic
তার পরেও চালু হয়নি।
এখন, _S_initialize_once()
নিজেই বরং সোজা: এটি বরাদ্দ করে _S_classic
এবং ভিতরে রাখে c_locale
.
void locale::_S_initialize_once() throw() { // Need to check this because we could get called once from _S_initialize() // when the program is single-threaded, and then again (via __gthread_once) // when it's multi-threaded. if (_S_classic) return; // 2 references. // One reference for _S_classic, one for _S_global _S_classic = new (&c_locale_impl) _Impl(2); _S_global = _S_classic; new (&c_locale) locale(_S_classic); }
ক্র্যাশ দেখে মনে হচ্ছে আমরা কখনই এর মধ্য দিয়ে যাইনি _S_initialize_once()
সুতরাং আসুন সেখানে একটি ব্রেকপয়েন্ট রাখি এবং দেখি কি হয়।
এবং শুধু এই কাজ করে, আমরা ইতিমধ্যেই সন্দেহজনক কিছু লক্ষ্য করি।
আমরা পৌঁছাতে পারি _S_initialize_once()
কিন্তু ফায়ারফক্স বাইনারির মধ্যে নয়: পরিবর্তে, আমরা শুধুমাত্র liblgpllibs.so দ্বারা রপ্তানি করা সংস্করণে পৌঁছাতে পারি।
প্রকৃতপক্ষে, liblgpllibs.so স্ট্যাটিকভাবে libstdc++ এর সাথে যুক্ত, যেমন ফায়ারফক্স এবং liblgpllibs.so উভয়ই তাদের নিজস্ব এম্বেড এবং এক্সপোর্ট করে _S_initialize_once()
ফাংশন
ডিফল্টরূপে, প্রতীক ইন্টারপোজিশন প্রযোজ্য, এবং _S_initialize_once()
সর্বদা প্রক্রিয়া লিঙ্কেজ টেবিল (PLT) এর মাধ্যমে কল করা উচিত, যাতে প্রতিটি মডিউল ফাংশনের একই সংস্করণে কল করে।
যদি এখানে প্রতীক ইন্টারপোজিশন ঘটতে থাকে, আমরা আশা করব liblgpllibs.so _S_initialize_once() এর নিজস্ব সংস্করণের পরিবর্তে ফায়ারফক্স দ্বারা রপ্তানি করা সংস্করণে পৌঁছাবে, কারণ ফায়ারফক্স প্রথমে লোড করা হয়েছিল।
তাই হয়তো কোন প্রতীক ইন্টারপোজিশন নেই।
এটি ব্যবহার করার সময় ঘটতে পারে -fno-semantic-interposition
.
স্ট্যান্ডার্ড লাইব্রেরির প্রতিটি সংস্করণ অন্য সংস্করণ থেকে স্বতন্ত্রভাবে বেঁচে থাকবে।
কিন্তু ফায়ারফক্স বিল্ড সিস্টেম বা উবুন্টু রক্ষণাবেক্ষণকারী কেউই কম্পাইলারকে এই পতাকাটি পাস করে বলে মনে হচ্ছে না।
যাইহোক, জন্য disassembly দেখে _S_initialize()
এবং _S_initialize_once()
আমরা দেখতে পাচ্ছি যে রপ্তানি করা গ্লোবাল ভেরিয়েবল (_S_once
, _S_classic
, _S_global
) হয় প্রতীক ইন্টারপোজিশন সাপেক্ষে:
এই সমস্ত অ্যাক্সেসগুলি গ্লোবাল অফসেট টেবিলের (জিওটি) মাধ্যমে যায়, যাতে প্রতিটি মডিউল ভেরিয়েবলের একই সংস্করণ অ্যাক্সেস করে।
আমরা আগে যা বলেছি তা দেখে এটি অদ্ভুত বলে মনে হচ্ছে _S_initialize_once()
.
অ-রপ্তানিযোগ্য বৈশ্বিক ভেরিয়েবল (c_locale
, c_locale_impl
), যাইহোক, প্রত্যাশিত হিসাবে, প্রতীক ইন্টারপজিশন ছাড়াই সরাসরি অ্যাক্সেস করা হয়।
ক্র্যাশ ব্যাখ্যা করার জন্য আমাদের কাছে এখন যথেষ্ট তথ্য রয়েছে।
আমরা যখন পৌঁছি _S_initialize()
liblgpllibs.so এ, আমরা আসলে গ্রাস করি _S_once
যেটি ফায়ারফক্সে থাকে এবং শুরু করে _S_classic
এবং _S_global
যারা ফায়ারফক্সে থাকে।
কিন্তু আমরা ভাল ইনিশিয়ালাইজড ভেরিয়েবলের পয়েন্টার দিয়ে তাদের আরম্ভ করি c_locale_impl
এবং c_locale
যে liblgpllibs.so এ বাস!
ভেরিয়েবল c_locale_impl
এবং c_locale
যেগুলো ফায়ারফক্সে বাস করে, তবে, অপ্রচলিত থাকে।
তাই যদি আমরা পরে পৌঁছান _S_initialize()
ফায়ারফক্সে, সবকিছু দেখে মনে হচ্ছে যেন আরম্ভ হয়ে গেছে।
কিন্তু তারপর আমরা এর সংস্করণে একটি রেফারেন্স ফেরত দেই c_locale
যেটি ফায়ারফক্সে থাকে, এবং এই সংস্করণটি কখনই আরম্ভ করা হয়নি।
বুম!
এখন মূল প্রশ্ন হল: কেন আমরা দেখতে পাচ্ছি ইন্টারপোজিশন ঘটতে _S_once
কিন্তু জন্য না _S_initialize_once()
?
যদি আমরা এক মিনিটের জন্য পিছিয়ে যাই, তাহলে এই চিহ্নগুলির মধ্যে একটি মৌলিক পার্থক্য রয়েছে: একটি হল একটি ফাংশন প্রতীক, অন্যটি একটি পরিবর্তনশীল প্রতীক।
এবং প্রকৃতপক্ষে, ফায়ারফক্স বিল্ড সিস্টেম ব্যবহার করে -Bsymbolic-function
পতাকা
ld man পৃষ্ঠাটি এটিকে নিম্নরূপ বর্ণনা করে:
-Bsymbolic-functions When creating a shared library, bind references to global function symbols to the definition within the shared library, if any. This option is only meaningful on ELF platforms which support shared libraries.
এর বিপরীতে:
-Bsymbolic When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a program linked against a shared library to override the definition within the shared library. This option is only meaningful on ELF platforms which support shared libraries.
এটা পেরেক!
ক্র্যাশ ঘটে কারণ এই পতাকাটি আমাদেরকে প্রতীক ইন্টারপোজিশনের একটি অদ্ভুত বৈকল্পিক ব্যবহার করে, যেখানে পরিবর্তনশীল চিহ্নগুলির জন্য প্রতীক ইন্টারপোজিশন ঘটে _S_once
এবং _S_classic
কিন্তু ফাংশন চিহ্নের জন্য নয় যেমন _S_initialize_once()
.
এর ফলে আমরা কীভাবে গ্লোবাল ভেরিয়েবল অ্যাক্সেস করি সে সম্পর্কে একটি অমিল হয়: রপ্তানি করা গ্লোবাল ভেরিয়েবলগুলি ইন্টারপোজিশনের জন্য অনন্য ধন্যবাদ, যেখানে প্রতিটি নন-ইন্টারপোজড ফাংশন যেকোনো অ-রপ্তানিযোগ্য বৈশ্বিক ভেরিয়েবলের নিজস্ব সংস্করণ অ্যাক্সেস করবে।
আমরা এখন যে সমস্ত জ্ঞান সংগ্রহ করেছি তা দিয়ে, কোনো ফায়ারফক্স কোড জড়িত নয় এমন একটি প্রজননকারী লেখা সহজ:
/* main.cc */ #include <iostream> extern void pain(); int main() { pain(); std::cout << "(main) " << std::locale::classic().name() <<"\n"; return 0; } /* pain.cc */ #include <iostream> void pain() { std::cout << "(pain) " << std::locale::classic().name() <<"\n"; } # Makefile all: $(CXX) pain.cc -fPIC -shared -o libpain.so -static-libstdc++ -Wl,-Bsymbolic-functions $(CXX) main.cc -fPIC -c -o main.o $(CC) main.o -fPIC -o main /usr/lib/gcc/x86_64-redhat-linux/13/libstdc++.a -L. -Wl,-rpath=. -lpain -Wl,-Bsymbolic-functions ./main clean: $(RM) libpain.so main
বাগ বোঝা এক ধাপ, এবং এটি সমাধান করা অন্য গল্প।
এটিকে একটি libstdc++ বাগ হিসেবে বিবেচনা করা উচিত যেটির সাথে লোকেলের কোড সামঞ্জস্যপূর্ণ নয় -static-stdlibc++ -Bsymbolic-functions
?
মনে হচ্ছে এই পতাকাগুলিকে একত্রিত করা আমাদের নিজের কবর খননের একটি খুব সুন্দর উপায়, এবং৷ এটি আসলে libstdc++ রক্ষণাবেক্ষণকারীদের মতামত বলে মনে হচ্ছে.
সামগ্রিকভাবে, সম্ভবত এই গল্পের সবচেয়ে অদ্ভুত অংশ হল যে এই সংমিশ্রণটি এখন পর্যন্ত কোন সমস্যা সৃষ্টি করেনি।
অতএব, আমরা প্যাকেজটির রক্ষণাবেক্ষণকারীকে ব্যবহার বন্ধ করার পরামর্শ দিয়েছি -static-libstdc++
.
সিস্টেমে উপলব্ধ থেকে আলাদা libstdc++ ব্যবহার করার অন্যান্য উপায় রয়েছে, যেমন ডায়নামিক লিঙ্কিং ব্যবহার করা এবং একটি সেট করা RPATH
একটি বান্ডিল সংস্করণের সাথে লিঙ্ক করতে।
তা করছেন তাদের সফলভাবে প্যাকেজের একটি নির্দিষ্ট সংস্করণ স্থাপন করার অনুমতি দেয়।
এর কয়েকদিন পর, ফায়ারফক্স 120-এর অফিসিয়াল রিলিজের সাথে, আমরা একই ক্র্যাশ সিগনেচারের জন্য ভলিউমের একটি খুব গুরুত্বপূর্ণ বাম্প লক্ষ্য করেছি। আবার না!
এই সময় ভলিউম শুধুমাত্র NixOS 23.05 ব্যবহারকারীদের কাছ থেকে আসছে, এবং এটি বিশাল ছিল!
আমরা তাদের সাথে আমাদের বিটা তদন্তের সিদ্ধান্তগুলি ভাগ করার পরে, NixOS এর রক্ষণাবেক্ষণকারীরা সক্ষম হয়েছিল দ্রুত যুক্ত করা সঙ্গে ক্র্যাশ একটি সমস্যা যা এখনও ব্যাকপোর্ট করা হয়নি 23.05 এর জন্য এবং কম্পাইলারের মত আচরণ করার কারণ ছিল -static-libstdc++
.
ভবিষ্যতে এ ধরনের বিশৃঙ্খলা এড়াতে, আমরা Firefox এর কনফিগারে এই নির্দিষ্ট সেটআপের জন্য সনাক্তকরণ যোগ করেছি.
যারা এই সমস্যাটির সমাধান করতে সাহায্য করেছেন আমরা তাদের প্রতি কৃতজ্ঞ, বিশেষ করে:
- Rico Tzschichholz (ricotz) যিনি দ্রুত উবুন্টু 18.04 LTS প্যাকেজ ঠিক করেছিলেন এবং আমিন বান্দালি (বান্দালি) যিনি পথে সাহায্য করেছিলেন;
- NixOS 23.05 প্যাকেজের জন্য তাদের প্রম্পট ফিক্সের জন্য মার্টিন উইনেল্ট (হেক্সা) এবং আর্টুরিন;
- Nicolas B. Pierron (nbp) আমাদেরকে NixOS এর সাথে শুরু করতে সাহায্য করার জন্য, যা আমাদের দ্রুত NixOS প্যাকেজ রক্ষণাবেক্ষণকারীদের সাথে দরকারী তথ্য শেয়ার করতে দেয়।