Анализ связей таблиц лексем компиляции на примере хинтов¶
В ходе различных работ с Oracle периодически сталкивался с внутренними структурами qcplk_id
, qcplk_tab
и qcplk_str
, но не особо разбирал связь между ними.
Недавно, в очередной раз встретив данные структуры, решил провести подробный анализ их связей, чему посвящена данная заметка.
X$QKSHT¶
Для простого примера, рассмотрим X$QKSHT
(базовая структура для V$SQL_HINT
) в версии Oracle Database 19c (19.27):
SQL> select token_qkshtsyrow from x$qksht where rownum<=5;
TOKEN_QKSHTSYROW
----------------------------------------------------------------
APPEND
NOAPPEND
NO_MONITORING
NO_SQL_TUNE
DEREF_NO_REWRITE
С помощью xinfo определим функцию, формирующую структуру:
xinfo list 'X$QKSHT' --with-kqftap -o json
{
"821": {
"obj": 4294952777,
"ver": 2,
"nam_ptr": 355042636,
"nam": "X$QKSHT",
"xstruct_nam_ptr": 355042644,
"xstruct": "qkshtrow",
"typ": 4,
"flg": 0,
"rsz": 96,
"coc": 12,
"kqftap": {
"xstruct_ptr": 354430816,
"cb1_ptr": 167555808,
"xstruct": "qkshtrow_c",
"cb1": "qkshtCb"
}
}
}
Пройдя по цепочке вызовов от qkshtCb
и далее, можно выяснить, что базовой структурой для X$QKSHT
является qkshtDefaults
.
qkshtDefaults¶
Разбор структуры qkshtDefaults
удобно осуществить с помощью bide:
bide dump-table qkshtDefaults --format name:I sql_feature:I class:I inverse:I target_level:I property:I version:I I version_outline:I I --topn 6
+------+-------------+-------+---------+--------------+----------+----------+----------+-----------------+----------+
| name | sql_feature | class | inverse | target_level | property | version | unnamed7 | version_outline | unnamed9 |
+------+-------------+-------+---------+--------------+----------+----------+----------+-----------------+----------+
| 0 | 0 | 0 | 0 | 0 | 0 | 8000000 | 8000000 | 100000000 | 0 |
| 814 | 4 | 814 | 864 | 1 | 0 | 8010000 | 8010000 | 100000000 | 0 |
| 864 | 4 | 814 | 814 | 1 | 0 | 8010000 | 8010000 | 100000000 | 0 |
| 877 | 0 | 877 | 0 | 1 | 0 | 8000000 | 8000000 | 100000000 | 0 |
| 1113 | 0 | 1113 | 0 | 1 | 0 | 10020001 | 10020001 | 100000000 | 0 |
| 827 | 0 | 827 | 0 | 1 | 0 | 8010000 | 8010000 | 100000000 | 2 |
+------+-------------+-------+---------+--------------+----------+----------+----------+-----------------+----------+
Отбросив первую строку, сравним вывод с V$SQL_HINT
:
SQL> select * from v$sql_hint where rownum<=5;
NAME SQL_FEATURE CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OUTLINE CON_ID
---------------- ----------- ---------------- ---------------- ------------ ---------- ---------- --------------- ----------
APPEND QKSFM_CBO APPEND NOAPPEND 1 0 8.1.0 0
NOAPPEND QKSFM_CBO APPEND APPEND 1 0 8.1.0 0
NO_MONITORING QKSFM_ALL NO_MONITORING 1 0 8.0.0 0
NO_SQL_TUNE QKSFM_ALL NO_SQL_TUNE 1 0 10.2.0.1 0
DEREF_NO_REWRITE QKSFM_ALL DEREF_NO_REWRITE 1 0 8.1.0 0
Анализируя различные данные, можно придти к выводу, что, например, кодам 814 и 864 соответствуют строки APPEND
и NOAPPEND
, соответственно, которые получаются из qcplk
структур.
Также Oracle оперирует в своём коде именно идентификаторами хинтов, а не именами (и qcplk
структуры используются не только для хинтов).
Таким образом, полезно знать взаимосвязь qcplk
структур.
Анализ связей qcplk структур¶
Рассмотрим каждую из структур в отдельности.
qcplk_id¶
Как правило, qcplk_id
является первой структурой, к которой происходит обращение.
Выглядит она следующим образом (используется bide):
bide dump-table qcplk_id --format id:H
+------+
| id |
+------+
| 2497 |
| 2498 |
| 2497 |
| 2497 |
| 536 |
| 560 |
| 580 |
...
По своей сути, это массив или таблица.
Обращения производятся напрямую по номеру строки.
Например, в строках 814 и 864 (которые, предположительно, должны соответствовать хинтам APPEND
и NOAPPEND
) находятся значения 2195 и 2027 соответственно.
Это номера строк в таблице qcplk_tab
.
qcplk_tab¶
Таблица qcplk_tab
имеет следующую структуру (получена с помощью bide):
bide dump-table qcplk_tab --format id:I I len:L offset:L
+------+----------+-----+--------+
| id | unnamed1 | len | offset |
+------+----------+-----+--------+
| 462 | 1 | 14 | 1 |
| 2036 | 1 | 5 | 16 |
| 345 | 1 | 7 | 22 |
| 2351 | 1 | 8 | 30 |
| 12 | 2 | 3 | 39 |
| 749 | 1 | 4 | 43 |
...
На основании данных из qcplk_id
, делается обращение к таблице qcplk_tab
по номеру строки.
Например, значениям 2195 и 2027 из qcplk_id
(соответствующим номерам хинтов 814 и 864) соответствуют следующие строки из qcplk_tab
:
+------+----------+-----+--------+
| id | unnamed1 | len | offset |
+------+----------+-----+--------+
| 814 | 1 | 6 | 24848 |
| 864 | 1 | 8 | 22808 |
+------+----------+-----+--------+
Описание полей:
id
: начальный идентификатор.len
: длина строки.offset
: смещение строки.
Смещение строки используется для доступа к последней таблице в исследуемой цепочке: qcplk_str
.
qcplk_str¶
Структура содержит строки, разделённые нулевым байтом:
readelf -s oracle | grep qcplk_str
106311: 0000000015fbaee0 28516 OBJECT GLOBAL DEFAULT 17 qcplk_str
176559: 0000000015fbaee0 28516 OBJECT GLOBAL DEFAULT 17 qcplk_str
objdump -s --start-address=0x0000000015fbaee0 oracle | head
oracle: file format elf64-x86-64
Contents of section .rodata:
15fbaee0 00534341 4e5f494e 5354414e 43455300 .SCAN_INSTANCES.
15fbaef0 5245414c 4d005245 56455253 45004156 REALM.REVERSE.AV
15fbaf00 5f434143 48450041 53430054 45535400 _CACHE.ASC.TEST.
15fbaf10 4552524f 525f4f4e 5f4f5645 524c4150 ERROR_ON_OVERLAP
15fbaf20 5f54494d 45004e4f 534f5254 004f574e _TIME.NOSORT.OWN
15fbaf30 45525348 49500053 59535f4f 505f5250 ERSHIP.SYS_OP_RP
Для удобства можно использовать следующий Python-скрипт, представляющий данные в табличной форме offset
, string
(используется bide):
import itertools
import bide
import bide.binutils as binutils
import bide.config.settings as settings
settings.ora_binary = "/u01/app/oracle/product/19.3.0/dbhome_1/bin/oracle"
dump = binutils.objdump_symbol("qcplk_str")
zero_offsets = (i for i, b in enumerate(dump) if b == 0)
for start, end in itertools.pairwise(zero_offsets):
print(start + 1, dump[start + 1 : end].decode("utf-8"))
Например, по смещениям 24848 и 22808 находятся искомые имена хинтов:
24848 APPEND
22808 NOAPPEND
Отметим, что это довольно нетипичная структура для Oracle. На основании того, что я анализировал, обычно Oracle хранит в своих структурах адреса строк.
Пример: получение имени хинта по его коду¶
Приведём полный алгоритм получения имени хинта по его коду.
- Взять код хинта (например, 814).
Код может быть из внутренних структур (например,
qkshtDefaults
) или из кода Oracle. - Перейти на соответствующую строку
qcplk_id
: в строке 814 находится значение 2195. - Перейти на соответствующую строку
qcplk_tab
: в строке 2195 содержится код хинта (814), длина имени (6) и смещение (24848). - По смещению взять строку из
qcplk_str
: по смещению 24848 находится строкаAPPEND
.
Замечания и выводы¶
Стоит отметить, что qcplk
таблицы используются не только для хинтов.
Например, они активно используются в обратном парсинге планов выполнения и некоторых других местах кода Oracle.
Описанный выше алгоритм должен использоваться и там.
Как я понимаю, это таблицы лексем компиляции, судя по названиям, данным и тому, где в коде Oracle они используются.
Короче говоря¶
- Показано, как связаны таблицы
qcplk_id
,qcplk_tab
,qcplk_str
. - Для демонстрации связи таблиц используются числовые идентификаторы хинтов, которыми Oracle оперирует в своём коде.
- Вероятно,
qcplk
таблицы являются таблицами лексем компиляции.