Перейти к содержанию

Анализ связей таблиц лексем компиляции на примере хинтов

В ходе различных работ с 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:

Первые 6 строк структуры qkshtDefaults
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 хранит в своих структурах адреса строк.

Пример: получение имени хинта по его коду

Приведём полный алгоритм получения имени хинта по его коду.

  1. Взять код хинта (например, 814). Код может быть из внутренних структур (например, qkshtDefaults) или из кода Oracle.
  2. Перейти на соответствующую строку qcplk_id: в строке 814 находится значение 2195.
  3. Перейти на соответствующую строку qcplk_tab: в строке 2195 содержится код хинта (814), длина имени (6) и смещение (24848).
  4. По смещению взять строку из qcplk_str: по смещению 24848 находится строка APPEND.

Замечания и выводы

Стоит отметить, что qcplk таблицы используются не только для хинтов. Например, они активно используются в обратном парсинге планов выполнения и некоторых других местах кода Oracle. Описанный выше алгоритм должен использоваться и там. Как я понимаю, это таблицы лексем компиляции, судя по названиям, данным и тому, где в коде Oracle они используются.

Короче говоря

  • Показано, как связаны таблицы qcplk_id, qcplk_tab, qcplk_str.
  • Для демонстрации связи таблиц используются числовые идентификаторы хинтов, которыми Oracle оперирует в своём коде.
  • Вероятно, qcplk таблицы являются таблицами лексем компиляции.