CreateUri
and memmove
. Автор: Реймонд Чен.Один клиент пытался понять, почему его программа вылетала с ошибкой
E_BOUNDS
("out of bounds"), возбуждаемой из вызова метода CreateUri
:combase!RoOriginateErrorW+0x50 wincorlib!Platform::Details::ReCreateFromException+0x40 contoso!`__abi_translateCurrentException'::`1'::catch$0+0x10 contoso!memmove+0x217f4 contoso!Windows::Foundation::IUriRuntimeClassFactory::CreateUri+0x44 contoso!Contoso::DashboardView::DashboardView_obj1_Bindings::Update_ViewModel_Layout_Groups+0x50 contoso!Contoso::DashboardView::DashboardView_obj1_Bindings::Update_ViewModel_Layout+0xe4 contoso!Contoso::DashboardView::DashboardView_obj1_Bindings::PropertyChanged+0x1134 contoso!XamlBindingInfo::XamlBindingTrackingBase::PropertyChanged+0x30Судя по стеку, процедура копирования памяти
memmove
вызывала высокоуровневое исключение RTL C++/CX E_BOUNDS
— что не имеет никакого смысла. Ещё более загадочно то, что memmove
была вызвана из метода CreateUri
интерфейса IUriRuntimeClassFactory
, но код клиента DashboardView
вообще не работает с URI. Похоже, что этот стек вызовов - это просто какая-то чепуха.Что ж, попробуем раскрутить эту чушь.
Что касается таинственного
memmove
: обратите внимание на смещение 0x217f4. Маловероятно, что размер примитивной функции копирования памяти превышает 100 Кб. Тогда что тут происходит? Ну, это просто какой-то код, который, вероятно, был перемещен в редко используемую кодовую страницу подальше от остального кода. Ну и так случилось, что ближайший к нему символ — memmove
:
xor ecx,ecx call contoso!__abi_translateCurrentException int 3 ; memmove+0x217f4Да, так и есть: это код повторного возбуждения исключения. Поскольку исключения случаются редко, то оптимизатор может переместить весь связанный с исключениями код в отдельные страницы, чтобы они не занимали драгоценное место в часто используемых страницах кода.
Хорошо, тогда почему же метод
CreateUri
выбрасывает исключение "out of bounds"?Подождите, а вы уверены, что это действительно метод
CreateUri
?Я посмотрел на записи выше по стеку и спросил себя: "почему код привязки данных (data binding) вызывает
CreateUri
?".Код привязки данных автоматически создается компилятором XAML; он не присутствует в исходном коде программы. Вместо того, чтобы пытаться понять, как собрать и построить их проект (чтобы я мог извлечь автоматически сгенерированный файл), возможно, у меня получится разобраться по стеку и исходному коду.
Одно из основных предположений о коде в целом состоит в том, что люди, которые пишут код — не садисты. Это означает, что имена функций обычно описывают то, что они делают, имена переменных обычно описывают то, что они представляют, и так далее. Поэтому, когда я вижу класс с именем
DashboardView_obj1_Bindings
, я предполагаю, что этот класс предназначен для работы с привязками некоторого объекта внутри DashboardView
, и поскольку у него есть метод с именем Update_ViewModel_Layout_Groups
, он, вероятно, как-то связан с обновлением привязки чего-то с именем, включающим в себя слова ViewModel, Layout и Groups.Я просмотрел
DashboardView.xaml
в исходном коде программы и, действительно, нашёл там слово ViewModel
в элементах, которые, как оказалось, связаны с привязкой данных:
<ContentControl Grid.Row="0" x:Name="TogglesGroup" IsTabStop="False" Width="360" Content="{x:Bind ViewModel.Layout.Groups[0], Mode=OneWay}" ContentTemplateSelector="{StaticResource DashboardGroupTemplateSelector}"/>Это было не первое использование
x:Bind
в разметке XAML, что не соответствует obj1
, но другие части совпадают (Layout и Groups), поэтому я списал это на "возможно, компилятор XAML создаёт привязки в порядке, отличном от порядка их появления в разметке».Как эта привязка могла вызвать исключение "out of bounds"? Ну, тут записана операция с индексом 0, так что, возможно, коллекция Groups была пуста?
Я посмотрел на метод
Update_ViewModel_Layout_Groups
, чтобы убедиться, что эта теория соответствует действительности:
; Update_ViewModel_Layout_Groups: test rdx,rdx je ... mov qword ptr [rsp+8],rbx mov qword ptr [rsp+18h],rbp push rsi push rdi push r14 sub rsp,20h mov rbp,rdx mov rsi,rcx test r8d,0C0000001h je ... xor edx,edx mov rcx,rbp call contoso!Windows::Foundation::IUriRuntimeClassFactory::CreateUriФункция начинается с раннего завершения (без формирования стекового фрейма), если первый параметр равен нулю (это код метода C++, поэтому
RCX
содержит Self, а RDX
содержит первый "настоящий" параметр метода). Я понятия не имею, как работает связывание, поэтому предположим, что это часть его логики.А если параметр не равен нулю, то мы создаем правильный фрейм стека, проверяем некоторые биты в третьем параметре и, если они установлены, мы вызываем... простите, что?
CreateUri
с nil
? Это же бессмысленно. XAML не запрашивает URI, зачем ему пытаться создать URI из пустой строки?Опытный разработчик сообразит, что клиент (снова) был обманут оптимизатором.
Параметр Self для вызова
CreateUri
должен быть IUriRuntimeClassFactory
, но это не то, что мы передаем: мы передаем первый формальный параметр (который не является IUriRuntimeClassFactory
). Это значит, что перед нами вовсе не вызов CreateUri
.На самом деле, это — вызов метода
GetAt
интерфейса IVector
, а параметр метода равен нулю, потому что нам нужен объект с нулевым индексом. Методы GetAt
и CreateUri
были свёрнуты оптимизатором (что означает, что они занимают одно и то же место в машинном коде), потому что они побайтно идентичны! Они оба реализуют логику "вызови метод с одним параметром по индексу 6 из VMT объекта". Для IUriRuntimeClassFactory
этим методом будет CreateUri
со строковым параметром, а для IVector
этот метод — GetAt
, а параметр — индекс.Благодаря этому объяснению клиент понял, что у него действительно была нерешенная проблема, которая гласила: "Если наш файл настроек поврежден, то у нас не будет групп", а это исключение просто являлось альтернативным проявлением этой проблемы.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.