"Z80asm" कोडांतरक को एक ज्ञात मेमोरी पते पर एक निर्देश दें
मैं अपने होमब्रे Z80 कंप्यूटर के लिए एक बहुत ही मूल OS लिख रहा हूँ। एक पूर्ण असेंबली लैंग्वेज की शुरुआत के रूप में, मैं एक "ओएस प्लस मेमोरी मॉनिटर" काम करने में कामयाब रहा, जो रैम में मेमोरी कंटेंट और बाइट्स लोड कर सकता है। ऐसा करते हुए, मैंने कुछ I / O उपकरणों को इंटरफ़ेस करने के लिए "सिस्टम रूटीन" लिखा। उदाहरण के लिए, मेरे पास "प्रिंटक" रूटीन है जो एक बाइट को पढ़ता है और स्क्रीन पर संबंधित एएससीआईआई चरित्र को खींचता है।
यह कोडांतरक द्वारा निर्मित कोड के साथ काम कर रहा है क्योंकि कोडांतरक यह तय करता है कि रूटीन की पहली बाइट कहाँ रखी जाए और एक ही लेबल के साथ एक जेपी कमांड का सामना करने पर उस पते का उपयोग करता है।
अब, मैं प्रिंटेड रूटीन को डायनामिकली लोडेड प्रोग्राम से कॉल करना चाहूंगा। मैं यह बताने में सक्षम हूं कि कोडांतरक ने रॉम को -l
ध्वज के लिए रूटीन के पहले बाइट में कहां रखा है , जो आउटपुट युक्त है:
...
Print: equ $043a Printc: equ $043e
Readc: equ $0442 Readline: equ $0446
...
मैं अब इस तरह से एक कार्यक्रम लिख सकता हूं:
ld a, 0x50 ; ASCII code for P
call 0x043e ; Calls Printc
यह कार्यक्रम पी को सफलतापूर्वक प्रिंट करता है: मैंने अपने मेमोरी एड्रेस का उपयोग करके अपने प्रिंटेक रूटीन को बुलाया।
यह तब तक ठीक है जब तक कि मैं अपने "OS" में Printc घोषणा से पहले किसी भी विधानसभा कोड को नहीं बदलता। यदि मैं करता हूं, तो Printc लेबल दूसरे पते पर सौंपा जाएगा और मेरा मौजूदा कार्यक्रम काम करना बंद कर देगा।
इस प्रकार की समस्या के लिए विहित समाधान क्या है? केवल एक ही मेरे दिमाग में आता है, मेरे असेंबली कोड की शुरुआत में "जंप टेबल" बनाना, किसी भी आयात से पहले, सिस्टम कॉल की सूची के साथ, उम्मीद है कि वे हर बार एक ही पते पर प्राप्त करेंगे। कुछ इस तरह:
...
; System routines
Sys_Print:
call Print
ret
Sys_Printc:
call Printc
ret
.... and so on
लेकिन यह बहुत हैकिश लगता है ... क्या z80asmमेरे द्वारा तय किए गए मेमोरी एड्रेस पर नियमित रूप से पहला निर्देश देने के लिए कोडांतरक को निर्देश देना संभव है ?
जवाब
इस प्रकार की समस्या के लिए विहित समाधान क्या है?
कोई भी विहित समाधान नहीं है, लेकिन कई प्रकार, सभी उपयोगी पाए जा सकते हैं।
केवल एक चीज जो मेरे दिमाग में आती है वह है शुरुआत में "जंप टेबल" बनाना
जो कि एक परफेक्ट है। सिवाय, आमतौर पर एक व्यक्ति कोड लंबाई कम करने, निष्पादन में तेजी लाने और स्टैक लोड कम करने के लिए कॉल के बजाय जंप का उपयोग करेगा।
JUMP_TABLE:
PRINT JP _I_PRINT ; First Function
READC JP _I_READC ; Second Function
...
लेकिन यह बहुत hackish लगता है ...
नहीं, कई 8080 और Z80 सिस्टम ऐसे ही काम करते हैं।
आगे मुख्य कदम यह है कि सभी प्रवेश बिंदु एकल परिभाषित स्थान और अनुक्रम पर हैं।
क्या मेरे द्वारा तय किए गए स्मृति पते पर z80asm कोडांतरक को दिनचर्या का पहला निर्देश देना संभव है?
ज़रूर, जो भी आप इसे चाहते हैं, उसे ओआरजी का उपयोग करें (* 1)। लेकिन यह हैकिश होगा या कम से कम बहुत आगे की ओर देखने वाला नहीं होगा। परिभाषित पते पर इस तरह की जंप टेबल रखना एक शानदार शुरुआत है। बेशक यह कुछ जगह खाती है। प्रवेश प्रति तीन बाइट्स, लेकिन केवल दो पते हैं। क्या सिर्फ एड्रेस टेबल बनाना बेहतर नहीं होगा? पसंद:
SYS_TABLE:
DW _I_PRINT ; First Function
DW _I_READC ; Second Function
किसी फंक्शन को कॉल करना पसंद करेंगे
LD HL, (SYS_TABLE+0) ; Load Address of First Function - PRINT
JP (HL) ; Do it
यह आसानी से एक फ़ंक्शन चयनकर्ता के साथ जोड़ा जा सकता है:
SYS_ENTRY:
PUSH HL
LD H,0
LD L,A
ADD HL,HL
ADD HL,SYS_TABLE
JP (HL)
अब यहां तक कि जंप टेबल को आवश्यकतानुसार ROM (या RAM) में इधर-उधर ले जाया जा सकता है।
इसे कॉल करना एक फ़ंक्शन नंबर का उपयोग करके होगा - जैसे कि कई OS में है - बस फ़ंक्शन नंबर को A में रखें और डिफ़ॉल्ट सिस्टम एंट्री पॉइंट (SYS_ENTRY) को कॉल करें।
LD A,0 ; Print
CALL SYS_ENTRY
बेशक यह अधिक पठनीय हो जाता है यदि ओएस फ़ंक्शन संख्याओं के लिए समीकरणों का एक सेट प्रदान करता है :)
अब तक लोड किए गए प्रोग्राम को अभी भी टेबल एड्रेस (SYS_TABLE) या चयनकर्ता (SYS_ENTRY) के लिए प्रवेश बिंदु जानने की आवश्यकता है। अमूर्तता का अगला स्तर उनके पते को परिभाषित स्थान में ले जाएगा, जैसे 0100h, शायद एक जेपी के रूप में सबसे अच्छा, इसलिए कोई भी उपयोगकर्ता प्रोग्राम हमेशा उस निश्चित पते (0100 h) को कॉल करता है, भले ही आपका ओएस रोम या रैम में या जहां भी हो।
और हाँ, अगर यह परिचित लगता है, तो यह उसी तरह है जैसे सीपी / एम सिस्टम कॉल को संभालता है, या एमएस-डॉस करता है।
MS-DOS की बात करें तो, यह OS फ़ंक्शन को कॉल करने के लिए एक अतिरिक्त (और अधिक सामान्य ज्ञात तरीका) प्रदान करता है, जिसे सॉफ्टवेयर-इंटरप्ट कहा जाता है, जैसे कि प्रसिद्ध INT 21h। और Z80 (और 8080 से पहले) की पेशकश के समान काफी कुछ है: आठ अलग रेस्टार्ट वैक्टर (0/8/16 / ...) का एक सेट। पुनरारंभ 0 रीसेट के लिए आरक्षित है, अन्य सभी का उपयोग किया जा सकता है। तो क्यों नहीं अपने ओएस के लिए दूसरे (आरएसटी 8 एच) का उपयोग कर रहे हैं? फंक्शन कॉल तब लगेगा:
LD A,0 ; Print
RST 8h
अब उपयोगकर्ता प्रोग्राम कोड ओएस संरचना और मेमोरी लेआउट से जितना संभव हो उतना अलग हो जाता है - बिना किसी रिलोकेटर या किसी की आवश्यकता के। सबसे अच्छी बात यह है कि, थोड़ी सी फ़िदालिंग के साथ, पूरे चयनकर्ता उपलब्ध 8 बाइट्स में फिट बैठता है, जिससे यह इष्टतम कोडिंग होता है।
थोड़ा सुझाव:
यदि आप इनमें से किसी भी मॉडल के लिए जाते हैं, तो सुनिश्चित करें कि आपके ओएस का पहला फ़ंक्शन (0) ओएस के बारे में जानकारी प्रदान करने वाला कॉल होगा, इसलिए प्रोग्राम संगतता की जांच कर सकते हैं। कम से कम दो बुनियादी मूल्यों को लौटाया जाना चाहिए:
- एबीआई जारी संख्या
- अधिकतम समर्थित फ़ंक्शन संख्या।
एबीआई रिहाई नंबर या एक संस्करण संख्या के रूप में ही नहीं हो सकता है, लेकिन करने के लिए नहीं है। इसे हर एपीआई परिवर्तन के साथ बढ़ाया जाना चाहिए। अधिकतम समर्थित फ़ंक्शन संख्या के साथ इस जानकारी का उपयोग उपयोगकर्ता प्रोग्राम द्वारा असंगतता के मामले में सुंदर को छोड़ने के लिए किया जा सकता है - दुर्घटनाग्रस्त मध्य मार्ग के बजाय। लक्ज़री के लिए फंक्शन के साथ-साथ पॉइंटर को भी लौटा सकते हैं
- OS जैसी संरचना के बारे में अधिक जानकारी युक्त संरचना
- पठनीय नाम / संस्करण
- विभिन्न स्रोतों के पते
- 'विशेष' प्रवेश बिंदु
- मशीन की जानकारी जैसे RAM आकार
- उपलब्ध इंटरफेस आदि।
बस केह रहा हू...
* 1 - और नहीं, कुछ के अलावा अन्य, ओआरजी को कभी भी अपने आप पैडिंग या एक जैसे नहीं जोड़ना चाहिए । ऐसा करने वाले असेंबलर्स एक बुरा विकल्प हैं। संगठन को केवल पता स्तर बदलना चाहिए, यह परिभाषित नहीं करना चाहिए कि किसी भी क्षेत्र में 'जंप ओवर' क्या है। ऐसा करने से संभावित त्रुटियों के स्तर बढ़ सकते हैं - कम से कम जैसे ही कुछ उन्नत ओआरजी उपयोग किया जाता है - मेरा विश्वास करो, जटिल संरचना करते समय ओआरजी एक बहुत ही बहुमुखी उपकरण है।
कुछ पैडिंग के साथ 'शून्य' क्षेत्रों को भरने के अलावा, इस पैडिंग का परिणाम अनछुए मेमोरी के बजाय कार्यक्रम का हिस्सा होगा, जो बाद के पैच के लिए एक मुख्य उपकरण को दूर ले जाएगा: अनइंस्टाल्यूटेड EPROM स्पेस। इन क्षेत्रों को केवल परिभाषित और लोड नहीं करने से, वे साफ स्थिति में रहेंगे (EPROM के मामले में सभी) और बाद में प्रोग्राम किया जा सकता है - उदाहरण के लिए डिबगिंग के दौरान कुछ कोड रखने के लिए, या बिना हॉट फ़िक्स लागू करने के लिए नए उपकरणों की प्रोग्रामिंग की जरूरत है।
तो अपरिभाषित स्मृति बस होनी चाहिए, अपरिभाषित। और यही कारण है कि जल्द से जल्द कोडांतरक आउटपुट / लोडर प्रारूप (लगता है कि मोटोरोला एसआरईसी या इंटेल एचईएक्स ) रॉम फैब्रिकेशन से प्रोग्राम डिलीवरी के लिए उपयोग किए जाने वाले सभी तरीकों से उपयोगकर्ता कार्यक्रमों के लिए क्षेत्रों को छोड़ने का एक तरीका है।
लंबी कहानी छोटी: अगर कोई चाहता है कि उसे भरा जाए, तो उसे पूरा करना होगा। z80asm इसे सही करता है।
Z80ASM के साथ विशेष रूप से समस्या यह है कि यह असेंबली इनपुट लेता है और एक स्थिर बाइनरी फ़ाइल को थूकता है। यह अच्छा और बुरा है।
"सामान्य" प्रणालियों में, पता असाइनमेंट अनिवार्य रूप से लिंकर की जिम्मेदारी है, कोडांतरक की नहीं। लेकिन असेंबलर्स सरल हैं कि कई निर्माण चक्र के उस पहलू को छोड़ देते हैं।
चूंकि Z80ASM "ऑब्जेक्ट" फ़ाइलों के बजाय शाब्दिक द्विआधारी छवियों को बाहर निकालता है, इसलिए इसे एक लिंकर की आवश्यकता नहीं है। लेकिन यह आपको आवश्यक रूप से वह नहीं करने देगा जो आप करना चाहते हैं।
सर्वव्यापी ओआरजी निर्देश पर विचार करें।
ओआरजी कोडांतरक को बताता है कि आगामी विधानसभा कोड के लिए प्रारंभ (मूल - इस प्रकार ओआरजी) पता क्या है।
इसका मतलब है अगर आप ऐसा करते हैं:
ORG 0x100
L1: jp L1
कोडर जेपीएम को जेएक्सपी को 0x100 (एल 1) के पते पर इकट्ठा करेगा।
लेकिन, जब वह द्विआधारी फ़ाइल बाहर थूकता है, तो फ़ाइल सिर्फ 3 बाइट्स होगी। बाइनरी फॉर्मेट में 0x100 के बाद जंप इंस्ट्रक्शन। इस फ़ाइल में कुछ भी नहीं है जो बताता है, अच्छी तरह से, कुछ भी, कि इसे "काम" के लिए 0x100 पर लोड किया जाना चाहिए। वह सूचना गायब है।
यदि तुम करो:
ORG 0x100
L1: jp L2
ORG 0x200
L2: jp L1
यह 6 बाइट्स वाली फ़ाइल का उत्पादन करने जा रहा है। यह उन दो जेपी निर्देशों को एक दूसरे के ठीक बाद डाल रहा है। ओआरजी बयान केवल यही बता रहा है कि लेबल क्या होना चाहिए। यह वह नहीं है जो आप उम्मीद करेंगे।
इसलिए, बस अपनी फ़ाइल में एक ओआरजी जोड़ने से वह नहीं होगा जो आप करना चाहते हैं, जब तक कि आपके पास उस विशिष्ट स्थान पर कोड लोड करने के लिए कोई वैकल्पिक विधि न हो जो आप अपना कोड चाहते हैं।
एकमात्र तरीका यह है कि Z80ASM को बॉक्स से बाहर करने के लिए बाइट्स, खाली स्थान के ब्लॉक के साथ अपनी आउटपुट फ़ाइल को पैड करना है, जो आपके कोड को सही जगह पर रखने के लिए बाइनरी को भर देगा।
आम तौर पर, यह वही है जो लिंकर आपके लिए करता है। लिंकर का काम आपके कोड के टुकड़े टुकड़े करना है, और परिणामस्वरूप बाइनरी छवि बनाना है। यह सब आपके लिए करता है।
मेरे असेंबलर पर, जो एक लिंकर का उपयोग नहीं करता था, उसने एक इंटेल एचईएक्स फ़ाइल प्रारूप का उत्पादन किया जिसमें डेटा के प्रत्येक ब्लॉक के लिए वास्तविक पता शामिल है।
इसलिए, पिछले उदाहरण के लिए, इसने दो रिकॉर्ड बनाए होंगे। एक 0x100 के लिए किस्मत में था, दूसरा 0x200 के लिए, और फिर हेक्स लोडिंग प्रोग्राम चीजों को सही जगह पर रखेगा। यह एक और विकल्प है, लेकिन Z80ASM समर्थन करने के लिए प्रतीत नहीं होता है कि या तो।
इसलिए।
Z80ASM महान है यदि आप ROM की शुरुआत, मनमाने ढंग से, 0x1000 से कर रहे हैं। आप ORG करेंगे कि, एक परिणामी बाइनरी प्राप्त करें, और पूरी फाइल को एक EPROM में डाउनलोड करें। यह उसके लिए एकदम सही है।
लेकिन आप क्या करना चाहते हैं, इसके लिए आपको अपनी रूटीन को सही स्थानों पर ले जाने के लिए आपको कोड डालना होगा, या आपके लिए यह प्रकट करने के लिए किसी अन्य लोडर योजना के साथ आना होगा।
org
निर्देश विशेष रूप से करना चाहिए कि आप क्या पूछना। हालाँकि, z80asm अपने आउटपुट स्वरूप में थोड़ा सरल है। इसके बजाय आप ds
विशेष पतों पर दिनचर्या रखने के लिए उपयोग कर सकते हैं :
ds 0x1000
printc:
...
ret
ds 0x1100-$
readc:
...
ret
यह हमेशा printc
0x1000 और readc
0x1100 पर लगाया जाएगा। इसके कई नुकसान हैं। printc
0x100 से बड़ा होना चाहिए , कार्यक्रम इकट्ठा नहीं होगा और आपको printc
कुछ फैशन में अलग होने और अतिरिक्त कोड को कहीं और लगाने की आवश्यकता होगी । उसके लिए और अन्य कारणों से मेमोरी में एक निश्चित स्थान पर एक जंप टेबल को प्रबंधित करना और अधिक लचीला होना आसान है:
ds 0x100
v_printc: jp printc
v_readc: jp readc
...
एक अन्य तकनीक एकल प्रविष्टि बिंदु का उपयोग करना और A
रजिस्टर में एक मूल्य का उपयोग करके फ़ंक्शन चुनना है । यह कम से कम थोड़ा धीमा होगा लेकिन इसका मतलब यह है कि ऑपरेटिंग सिस्टम में बदलाव के लिए केवल एक प्रविष्टि बिंदु को बनाए रखने की आवश्यकता है।
और CALL
प्रविष्टि बिंदु पर करने के बजाय इसे विशेष RST
स्थानों (0, 8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38) RST 0x18
में से किसी एक स्थान पर रखें जहाँ आप एकल बाइट कॉल के रूप में स्मृति स्थान 0x18 का उपयोग कर सकते हैं । आमतौर पर RST 0
और RST 0x38
एक से बचा जाता है क्योंकि वे क्रमशः pwoer-on प्रविष्टि बिंदु और बाधा मॉडल 1 हैंडलर स्थान हैं।