อุปกรณ์ Android แต่ละประเภทใช้ CPU ที่แตกต่างกัน จึงรองรับการใช้งานที่ต่างกันด้วย ชุดคำสั่ง CPU และชุดคำสั่งแต่ละชุดมีหน่วยของตัวเอง อินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน (ABI) ABI ประกอบด้วยข้อมูลต่อไปนี้
- ชุดคำสั่งของ CPU (และส่วนขยาย) ที่ใช้ได้
- จุดสิ้นสุดของหน่วยความจำที่จัดเก็บและโหลดขณะรันไทม์ ใช้ Android เสมอ Little-endian
- แบบแผนสำหรับการส่งข้อมูลระหว่างแอปพลิเคชันและระบบ รวมถึง ข้อจำกัดการจัดแนว และวิธีที่ระบบใช้สแต็กและ จะลงทะเบียนเมื่อมีการเรียกใช้ฟังก์ชัน
- รูปแบบของไบนารีที่สั่งการได้ เช่น โปรแกรมและไลบรารีที่ใช้ร่วมกัน รวมถึงประเภทเนื้อหาที่รองรับ Android ใช้ ELF เสมอ สำหรับข้อมูลเพิ่มเติม ดูข้อมูลได้ที่ อินเทอร์เฟซแบบไบนารีของแอปพลิเคชันของระบบ ELF
- ชื่อ C++ ทำให้สับสนได้อย่างไร สำหรับข้อมูลเพิ่มเติม โปรดดู General/Itanium C++ ABI
หน้านี้แจกแจง ABI ที่ NDK รองรับ และให้ข้อมูล เกี่ยวกับวิธีการทำงานของ ABI แต่ละรายการ
ABI ยังสามารถหมายถึง API แบบเนทีฟที่แพลตฟอร์มรองรับได้ด้วย สำหรับ รายการปัญหา ABI ประเภทนี้ที่ส่งผลกระทบต่อระบบ 32 บิต โปรดดู ข้อบกพร่อง ABI 32 บิต
ABI ที่รองรับ
ตาราง 1 ABI และชุดคำสั่งที่รองรับ
ABI | ชุดวิธีการที่รองรับ | หมายเหตุ |
---|---|---|
armeabi-v7a |
|
ใช้ร่วมกับอุปกรณ์ ARMv5/v6 ไม่ได้ |
arm64-v8a |
Armv8.0 เท่านั้น | |
x86 |
ไม่รองรับ MOVBE หรือ SSE4 | |
x86_64 |
|
x86-64-v1 แบบเต็ม แต่มีเพียง x86-64-v2 บางส่วนเท่านั้น (ไม่มี CMPXCHG16B หรือ LAHF-SAHF) |
หมายเหตุ: ก่อนหน้านี้ NDK รองรับ ARMv5 (armeabi) และ MIPS แบบ 32 บิตและ 64 บิต แต่การรองรับ ABI เหล่านี้ถูกนําออกใน NDK r17
Armeabi-V7a
ABI นี้มีไว้สำหรับ CPU แบบ ARM 32 บิต โดยมี Thumb-2 และนีออน
สำหรับข้อมูลเกี่ยวกับส่วนต่างๆ ของ ABI ที่ไม่เกี่ยวกับ Android โดยเฉพาะ โปรดดู อินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน (ABI) สำหรับสถาปัตยกรรม ARM
ระบบบิลด์ของ NDK จะสร้างรหัส Thumb-2 โดยค่าเริ่มต้น เว้นแต่คุณจะใช้
LOCAL_ARM_MODE
ใน Android.mk
สำหรับ
ndk-build หรือ ANDROID_ARM_MODE
เมื่อกำหนดค่า CMake
ดูข้อมูลเพิ่มเติมเกี่ยวกับประวัติของ Neon ได้ที่ทีมสนับสนุนของ Neon
สำหรับเหตุผลที่ผ่านมา ABI นี้ใช้ -mfloat-abi=softfp
ที่ทําให้เกิด float
ทั้งหมด
ค่าที่จะส่งผ่านในการลงทะเบียนจำนวนเต็มและค่า double
ทั้งหมดที่จะส่ง
เป็นคู่การลงทะเบียนจำนวนเต็มเมื่อเรียกใช้ฟังก์ชัน แม้ว่าจะมีชื่อ
มีผลกับแบบแผนการเรียกใช้จุดลอยตัวเท่านั้น กล่าวคือคอมไพเลอร์จะยังคง
ให้ใช้คำสั่งจุดลอยตัวของฮาร์ดแวร์สำหรับเลขคณิต
ABI นี้ใช้ long double
64 บิต (IEEEไบนารี64 เดียวกับ double
)
ARM64-V8a
ABI นี้มีไว้สำหรับ CPU แบบ ARM 64 บิต
ดู Arm ศึกษาสถาปัตยกรรม เพื่อดูรายละเอียดทั้งหมดเกี่ยวกับส่วนต่างๆ ของ ABI ที่ไม่ได้มีไว้สำหรับ Android กลุ่ม ยังให้คำแนะนำการย้าย การพัฒนา Android 64 บิต
คุณใช้ Neon Intrinsics ได้ ในโค้ด C และ C++ เพื่อใช้ประโยชน์จากส่วนขยาย SIMD ขั้นสูง คู่มือของ Neon Programmer สำหรับ Armv8-A ให้ข้อมูลเพิ่มเติมเกี่ยวกับภายในของ Neon และการเขียนโปรแกรม Neon โดยทั่วไป
สำหรับ Android การลงทะเบียน x18 เฉพาะแพลตฟอร์มจะสงวนไว้สำหรับ
ShadowCallStack
และไม่ควรสัมผัสโค้ด Clang เวอร์ชันปัจจุบันมีค่าเริ่มต้นเป็น
โดยใช้ตัวเลือก -ffixed-x18
ใน Android ดังนั้น นอกจากคุณจะเขียนด้วยลายมือ
Assistant (หรือคอมไพเลอร์ที่เก่ามาก) คุณไม่ควรกังวลในเรื่องนี้
ABI นี้ใช้ long double
แบบ 128 บิต (ไบนารี IEEE128)
X86
ABI นี้มีไว้สำหรับ CPU ที่รองรับชุดคำสั่งที่เรียกกันโดยทั่วไปว่า "x86" "i386" หรือ "IA-32"
ABI ของ Android มีชุดคำสั่งพื้นฐานบวก MMX SSE SSE2 SSE3 และ ส่วนขยาย SSSE3
ABI ไม่รวมชุดคำสั่ง IA-32 อื่นๆ ส่วนขยาย เช่น MOVBE หรือ SSE4 เวอร์ชันใดก็ตาม คุณจะยังใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้การตรวจสอบฟีเจอร์รันไทม์เพื่อ เปิดใช้งานและแสดงรายการสำรองสำหรับอุปกรณ์ที่ไม่รองรับ
เครื่องมือเชน NDK จะถือว่ามีการจัดแนวสแต็ก 16 ไบต์ก่อนการเรียกใช้ฟังก์ชัน เครื่องมือเริ่มต้นและ ตัวเลือกนี้จะบังคับใช้กฎนี้ หากเขียนโค้ดการประกอบ คุณต้องตรวจสอบว่าได้ดูแลรักษาสแต็ก ความสอดคล้อง และตรวจสอบว่าคอมไพเลอร์อื่นๆ ปฏิบัติตามกฎนี้ด้วย
โปรดดูรายละเอียดเพิ่มเติมในเอกสารต่อไปนี้
- การโทร สำหรับคอมไพเลอร์และระบบปฏิบัติการ C++ แบบต่างๆ
- คู่มือสำหรับนักพัฒนาซอฟต์แวร์ Intel Architecture ของ Intel IA-32 ฉบับที่ 2: ข้อมูลอ้างอิงชุดคำสั่ง
- อินเทล คู่มือของนักพัฒนาซอฟต์แวร์สถาปัตยกรรม Intel ในสถาปัตยกรรมของ IA-32 ฉบับที่ 3: ระบบ คู่มือการจัดโปรแกรม
- ไบนารีแอปพลิเคชันของระบบ V อินเทอร์เฟซ: การเสริมสถาปัตยกรรมโปรเซสเซอร์ Intel386
ABI นี้ใช้ long double
แบบ 64 บิต (IEEEไบนารี64 เหมือนกับ double
และไม่ได้ใช้มากกว่า
Intel-only 80 บิต long double
ทั่วไป)
X86_64
ABI นี้มีไว้สำหรับ CPU ที่รองรับชุดคำสั่งที่เรียกกันโดยทั่วไปว่า "x86-64"
ABI ของ Android มีชุดคำสั่งพื้นฐานบวก MMX SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 และ คำสั่ง POPCNT
ABI ไม่รวมชุดคำสั่ง x86-64 อื่นๆ ส่วนขยาย เช่น MOVBE, SHA หรือ AVX เวอร์ชันใดก็ตาม คุณจะยังใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้ฟีเจอร์รันไทม์ที่ตรวจสอบ เปิดใช้งานและแสดงรายการสำรองสำหรับอุปกรณ์ที่ไม่รองรับ
โปรดดูรายละเอียดเพิ่มเติมในเอกสารต่อไปนี้
- ธรรมเนียมปฏิบัติสำหรับ คอมไพเลอร์และระบบปฏิบัติการ C++ ต่างๆ
- คู่มือสำหรับนักพัฒนาซอฟต์แวร์ Intel64 และ IA-32 Architectures ฉบับที่ 2: ชุดคำแนะนำ ข้อมูลอ้างอิง
- คู่มือสำหรับนักพัฒนาซอฟต์แวร์ Intel Architecture ของ Intel64 และ IA-32 ฉบับที่ 3: การเขียนโปรแกรมระบบ
ABI นี้ใช้ long double
แบบ 128 บิต (ไบนารี IEEE128)
สร้างโค้ดสำหรับ ABI ที่เฉพาะเจาะจง
เกรเดิล
Gradle (ไม่ว่าจะใช้ผ่าน Android Studio หรือจากบรรทัดคำสั่ง) สร้างสำหรับ
ABI ทั้งหมดที่ยังไม่เลิกใช้งานโดยค่าเริ่มต้น เพื่อจำกัดชุดของ ABI ที่
แอปพลิเคชันรองรับ ใช้ abiFilters
ตัวอย่างเช่น เพื่อสร้าง
ABI 64 บิต ให้ตั้งค่าการกำหนดค่าต่อไปนี้ใน build.gradle
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
Ndk-Build
โดยค่าเริ่มต้น บิลด์ ndk-build สำหรับ ABI ทั้งหมดที่ยังไม่ได้เลิกใช้งาน คุณสามารถกำหนดเป้าหมาย
ABI ที่ระบุโดยการตั้งค่า APP_ABI
ในไฟล์ Application.mk
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการใช้ APP_ABI
APP_ABI := arm64-v8a # Target only arm64-v8a
APP_ABI := all # Target all ABIs, including those that are deprecated.
APP_ABI := armeabi-v7a x86_64 # Target only armeabi-v7a and x86_64.
ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าที่คุณระบุสำหรับ APP_ABI
ได้ที่
Application.mk
ผู้ผลิต
เมื่อใช้ CMake คุณสามารถสร้างสำหรับ ABI รายการเดียวต่อครั้งและต้องระบุ ABI ของคุณ
อย่างชัดเจน โดยใช้ตัวแปร ANDROID_ABI
ซึ่งต้อง
ที่ระบุในบรรทัดคำสั่ง (ตั้งค่าใน CMakeLists.txt ไม่ได้) สำหรับ
ตัวอย่าง:
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
สำหรับแฟล็กอื่นๆ ที่ต้องส่งไปยัง CMake เพื่อสร้างด้วย NDK โปรดดู คู่มือ CMake
ลักษณะการทำงานเริ่มต้นของระบบบิลด์คือการรวมไบนารีสำหรับ ABI แต่ละรายการ ใน APK เดียว หรือที่เรียกว่า APK ที่มีไขมัน APK แบบอ้วนมีขนาดใหญ่กว่ามาก มากกว่า 1 รายการที่มีเฉพาะไบนารีสำหรับ ABI เดียว ข้อดีจะมาตอบแทน ความเข้ากันได้ที่กว้างขึ้น แต่ก็ต้องแลกกับ APK ที่ใหญ่กว่า สำคัญมาก ขอแนะนำให้คุณใช้ประโยชน์จาก App Bundle หรือการแยก APK เพื่อ ลดขนาด APK ในขณะที่ยังคงรักษาอุปกรณ์จำนวนสูงสุดไว้ ความสามารถในการใช้งานร่วมกัน
เมื่อทำการติดตั้ง ตัวจัดการแพ็กเกจจะคลายการแพคข้อมูลเฉพาะ รหัสเครื่องสำหรับอุปกรณ์เป้าหมาย โปรดดูรายละเอียดที่หัวข้อการดึงข้อมูลโดยอัตโนมัติ โค้ดเนทีฟในเวลาติดตั้ง
การจัดการ ABI บนแพลตฟอร์ม Android
ส่วนนี้จะให้รายละเอียดเกี่ยวกับวิธีที่แพลตฟอร์ม Android จัดการโฆษณาเนทีฟ ใน APK
โค้ดที่มาพร้อมเครื่องในแพ็กเกจแอป
ทั้ง Play Store และตัวจัดการแพ็กเกจคาดหวังว่าจะพบ NDK ที่ผลิตขึ้น ไลบรารีบนเส้นทางไฟล์ภายใน APK ที่ตรงกับรูปแบบต่อไปนี้
/lib/<abi>/lib<name>.so
ในที่นี้ <abi>
เป็นหนึ่งในชื่อ ABI ที่แสดงภายใต้ ABI ที่รองรับ
และ <name>
คือชื่อของไลบรารีตามที่คุณกำหนดสำหรับ LOCAL_MODULE
ในไฟล์ Android.mk
ตั้งแต่ปี
ไฟล์ APK จะเป็นแค่ไฟล์ ZIP การเปิดไฟล์เหล่านี้และยืนยันว่าโฆษณาเนทีฟที่แชร์
ห้องสมุดคือที่ที่พวกเขาอยู่
หากระบบไม่พบไลบรารีที่แชร์แบบเนทีฟในตำแหน่งที่คาดไว้ ไลบรารีจะใช้
ให้พวกเขา ในกรณีดังกล่าว แอปจะต้องคัดลอกไลบรารีมา จากนั้น
ดำเนินการ dlopen()
ใน APK ไฟล์ขนาดใหญ่ ไลบรารีแต่ละรายการจะอยู่ในไดเรกทอรีที่ชื่อตรงกับ ABI ที่สอดคล้องกัน ตัวอย่างเช่น APK แบบอ้วนอาจมีสิ่งต่อไปนี้
/lib/armeabi/libfoo.so /lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
หมายเหตุ: อุปกรณ์ Android ที่ใช้ ARMv7 ซึ่งใช้ 4.0.3 หรือเก่ากว่า
ติดตั้งไลบรารีแบบเนทีฟจากไดเรกทอรี armeabi
แทน armeabi-v7a
หากมีทั้งสองไดเรกทอรี เนื่องจาก /lib/armeabi/
ตามหลัง
/lib/armeabi-v7a/
ใน APK ปัญหานี้ได้รับการแก้ไขแล้วในเวอร์ชัน 4.0.4
การรองรับ ABI สำหรับแพลตฟอร์ม Android
ระบบ Android รู้ขณะรันไทม์ว่า ABI ใดรองรับ เนื่องจากระบบเฉพาะของบิลด์ พร็อพเพอร์ตี้ จะระบุสิ่งต่อไปนี้
- ABI หลักของอุปกรณ์ ซึ่งสอดคล้องกับรหัสเครื่องที่ใช้ใน อิมเมจของระบบเอง
- ABI รอง (ไม่บังคับ) ที่สอดคล้องกับ ABI อื่นๆ ที่อิมเมจระบบ ยังสนับสนุน
กลไกนี้จะช่วยให้ระบบดึงรหัสเครื่องที่ดีที่สุดออกมา แพ็กเกจเมื่อมีการติดตั้งได้
เพื่อประสิทธิภาพที่ดีที่สุด คุณควรคอมไพล์สำหรับ ABI หลักโดยตรง ตัวอย่างเช่น
อุปกรณ์ที่ใช้ ARMv5TE โดยทั่วไปจะกำหนด ABI หลักเท่านั้น: armeabi
ในทางตรงกันข้าม
โดยทั่วไป อุปกรณ์ที่ใช้ ARMv7 จะกำหนด ABI หลักเป็น armeabi-v7a
และอุปกรณ์รอง
เป็น armeabi
เนื่องจากสามารถเรียกใช้ไบนารีเนทีฟของแอปพลิเคชันที่สร้างขึ้นสำหรับแต่ละไฟล์
นอกจากนี้ อุปกรณ์ 64 บิตยังรองรับตัวแปร 32 บิตด้วย การใช้อุปกรณ์ arm64-v8a ตัวอย่างเช่น อุปกรณ์สามารถรันโค้ด armeabi และ armeabi-v7a ได้ หมายเหตุ แต่แอปพลิเคชันของคุณจะทำงานได้ดีกว่าในอุปกรณ์แบบ 64 บิตหาก กำหนดเป้าหมายเป็น arm64-v8a แทนที่จะใช้อุปกรณ์ที่ใช้ armeabi-v7a เวอร์ชันแอปพลิเคชันของคุณ
อุปกรณ์ที่ใช้ x86 จำนวนมากเรียกใช้ไบนารี NDK armeabi-v7a
และ armeabi
ได้ด้วย สำหรับ
อุปกรณ์ดังกล่าว ABI หลักจะเป็น x86
และอุปกรณ์ที่ 2 คือ armeabi-v7a
คุณบังคับติดตั้ง APK สำหรับ ABI ที่เจาะจงได้ ซึ่งมีประโยชน์สำหรับการทดสอบ ใช้คำสั่งต่อไปนี้
adb install --abi abi-identifier path_to_apk
การดึงข้อมูลโค้ดเนทีฟโดยอัตโนมัติเมื่อติดตั้ง
เมื่อติดตั้งแอปพลิเคชัน บริการตัวจัดการแพ็กเกจจะสแกน APK และมองหา ไลบรารีที่ใช้ร่วมกันของแบบฟอร์ม:
lib/<primary-abi>/lib<name>.so
ถ้าไม่พบ และคุณได้กำหนด ABI รองแล้ว บริการจะสแกนหาไลบรารีที่ใช้ร่วมกันของ แบบฟอร์ม:
lib/<secondary-abi>/lib<name>.so
เมื่อพบไลบรารีที่ต้องการ ตัวจัดการแพ็กเกจจะทำสำเนา
ไปยัง /lib/lib<name>.so
ภายใต้ไดเรกทอรีไลบรารีเนทีฟของแอปพลิเคชัน
(<nativeLibraryDir>/
) ข้อมูลโค้ดต่อไปนี้จะดึง nativeLibraryDir
Kotlin
import android.content.pm.PackageInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager ... val ainfo = this.applicationContext.packageManager.getApplicationInfo( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ) Log.v(TAG, "native library dir ${ainfo.nativeLibraryDir}")
Java
import android.content.pm.PackageInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; ... ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo ( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ); Log.v( TAG, "native library dir " + ainfo.nativeLibraryDir );
หากไม่มีไฟล์ออบเจ็กต์ที่ใช้ร่วมกัน แอปพลิเคชันจะสร้างและติดตั้ง แต่เกิดข้อขัดข้อง รันไทม์
ARMv9: เปิดใช้ PAC และ BTI สำหรับ C/C++
การเปิดใช้ PAC/BTI จะช่วยป้องกันเวกเตอร์การโจมตีบางอย่าง PAC ปกป้องที่อยู่สำหรับส่งกลับโดยการลงชื่อที่อยู่เหล่านั้นแบบเข้ารหัสในฟังก์ชัน Prolog และตรวจสอบว่าที่อยู่สำหรับคืนสินค้ายังคงมีการลงชื่อเข้าใช้อย่างถูกต้อง บทสรุป BTI ป้องกันการข้ามไปยังตำแหน่งที่กำหนดเองในโค้ดของคุณโดยกำหนดให้ แต่ละเป้าหมายสาขาเป็นคำสั่งพิเศษที่ไม่ทำอะไรนอกจากบอก โปรเซสเซอร์ว่า คุณสามารถลงจอดที่นั่นได้
Android ใช้วิธีการของ PAC/BTI ซึ่งไม่ดำเนินการใดๆ กับหน่วยประมวลผลรุ่นเก่าที่ ไม่สนับสนุนวิธีการใหม่ เฉพาะอุปกรณ์ ARMv9 เท่านั้นที่จะมี PAC/BTI แต่คุณสามารถเรียกใช้โค้ดเดียวกันนี้บนอุปกรณ์ ARMv8 ได้เช่นกัน โดยไม่จำเป็นต้อง ไลบรารีรูปแบบต่างๆ แม้จะในอุปกรณ์ ARMv9 แต่ระบบจะใช้ PAC/BTI เท่านั้น เป็นรหัส 64 บิต
การเปิดใช้ PAC/BTI จะทำให้ขนาดโค้ดเพิ่มขึ้นเล็กน้อย ซึ่งโดยปกติแล้วจะอยู่ที่ 1%
ดู Arm's เรียนรู้สถาปัตยกรรม - ให้การปกป้องสำหรับ ซอฟต์แวร์ที่ซับซ้อน (PDF) เพื่อดูคำอธิบายโดยละเอียดเกี่ยวกับเวกเตอร์การโจมตีเป้าหมาย PAC/BTI และ การป้องกันจึงได้ผล
สร้างการเปลี่ยนแปลง
Ndk-Build
ตั้งค่า LOCAL_BRANCH_PROTECTION := standard
ในแต่ละโมดูลของ Android.mk
ผู้ผลิต
ใช้ target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
สำหรับแต่ละเป้าหมายใน CMakeLists.txt
ระบบบิลด์อื่นๆ
คอมไพล์โค้ดโดยใช้ -mbranch-protection=standard
แฟล็กนี้ใช้งานได้เท่านั้น
เมื่อคอมไพล์สำหรับ aBI ที่ arm64-v8a คุณไม่จําเป็นต้องใช้แฟล็กนี้เมื่อ
และการลิงก์
การแก้ปัญหา
เราไม่พบปัญหาใดๆ เกี่ยวกับการรองรับคอมไพเลอร์สำหรับ PAC/BTI แต่
- โปรดระวังอย่ารวมรหัส BTI และรหัสที่ไม่ใช่ BTI ขณะลิงก์ เนื่องจาก ผลการค้นหาในคลังที่ไม่ได้เปิดใช้การป้องกัน BTI คุณสามารถใช้ llvm-readelf เพื่อตรวจสอบว่าไลบรารีที่ได้มีบันทึก BTI หรือไม่
$ llvm-readelf --notes LIBRARY.so [...] Displaying notes found in: .note.gnu.property Owner Data size Description GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0 (property note) Properties: aarch64 feature: BTI, PAC [...] $
OpenSSL เวอร์ชันเก่า (ก่อน 1.1.1i) มีข้อบกพร่องใน Ascyclr ที่เขียนด้วยลายมือ ที่ทำให้ PAC ล้มเหลว อัปเกรดเป็น OpenSSL ปัจจุบัน
ระบบ DRM ของแอปเวอร์ชันเก่าบางระบบสร้างโค้ดที่ละเมิด PAC/BTI หากคุณใช้ DRM ของแอปและพบปัญหาเมื่อเปิดใช้ PAC/BTI โปรดติดต่อผู้ให้บริการ DRM เพื่อขอเวอร์ชันที่แก้ไขแล้ว